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

@ -1,556 +1,225 @@
extends Panel
extends Node2D
@onready var _script_list = $ScriptsList
@onready var _nav_container = $VBox/BottomPanel/VBox/HBox/Node3D
@onready var _nav = {
container = _nav_container,
prev = _nav_container.get_node("VBox/HBox/Previous"),
next = _nav_container.get_node("VBox/HBox/Next"),
run = _nav_container.get_node("VBox/HBox/Run"),
current_script = _nav_container.get_node("VBox/CurrentScript"),
run_single = _nav_container.get_node("VBox/HBox/RunSingleScript")
}
@onready var _progress_container = $VBox/BottomPanel/VBox/HBox/Progress
@onready var _progress = {
script = _progress_container.get_node("ScriptProgress"),
script_xy = _progress_container.get_node("ScriptProgress/xy"),
test = _progress_container.get_node("TestProgress"),
test_xy = _progress_container.get_node("TestProgress/xy")
}
@onready var _summary = {
control = $VBox/TitleBar/HBox/Summary,
failing = $VBox/TitleBar/HBox/Summary/Failing,
passing = $VBox/TitleBar/HBox/Summary/Passing,
asserts = $VBox/TitleBar/HBox/Summary/AssertCount,
fail_count = 0,
pass_count = 0
}
class GuiHandler:
var _gui = null
var _gut = null
@onready var _extras = $ExtraOptions
@onready var _ignore_pauses = $ExtraOptions/IgnorePause
@onready var _continue_button = $VBox/BottomPanel/VBox/HBox/Continue/Continue
@onready var _text_box = $VBox/TextDisplay/RichTextLabel
@onready var _text_box_container = $VBox/TextDisplay
@onready var _log_level_slider = $VBox/BottomPanel/VBox/HBox2/LogLevelSlider
@onready var _resize_handle = $ResizeHandle
@onready var _current_script = $VBox/BottomPanel/VBox/HBox2/CurrentScriptLabel
@onready var _title_replacement = $VBox/TitleBar/HBox/TitleReplacement
var _ctrls = {
btn_continue = null,
path_dir = null,
path_file = null,
prog_script = null,
prog_test = null,
rtl = null,
rtl_bg = null,
time_label = null
}
@onready var _titlebar = {
bar = $VBox/TitleBar, time = $VBox/TitleBar/HBox/Time, label = $VBox/TitleBar/HBox/Title
}
func _init(gui):
_gui = gui
@onready var _user_files = $UserFileViewer
# Brute force, but flexible.
_ctrls.btn_continue = _get_first_child_named('Continue', _gui)
_ctrls.path_dir = _get_first_child_named('Path', _gui)
_ctrls.path_file = _get_first_child_named('File', _gui)
_ctrls.prog_script = _get_first_child_named('ProgressScript', _gui)
_ctrls.prog_test = _get_first_child_named('ProgressTest', _gui)
_ctrls.rtl = _get_first_child_named('Output', _gui)
_ctrls.rtl_bg = _get_first_child_named('OutputBG', _gui)
_ctrls.time_label = _get_first_child_named('TimeLabel', _gui)
var _mouse = {down = false, in_title = false, down_pos = null, in_handle = false}
_ctrls.btn_continue.visible = false
_ctrls.btn_continue.pressed.connect(_on_continue_pressed)
var _is_running = false
var _start_time = 0.0
var _time = 0.0
_ctrls.prog_script.value = 0
_ctrls.prog_test.value = 0
_ctrls.path_dir.text = ''
_ctrls.path_file.text = ''
_ctrls.time_label.text = ''
const DEFAULT_TITLE = "GUT"
var _pre_maximize_rect = null
var _font_size = 20
var _compact_mode = false
var min_sizes = {
compact = Vector2(330, 100),
full = Vector2(740, 300),
}
# ------------------
# Events
# ------------------
func _on_continue_pressed():
_ctrls.btn_continue.visible = false
_gut.end_teardown_pause()
signal end_pause
signal ignore_pause
signal log_level_changed
signal run_script
signal run_single_script
func _on_gut_start_run():
if(_ctrls.rtl != null):
_ctrls.rtl.clear()
set_num_scripts(_gut.get_test_collector().scripts.size())
func _on_gut_end_run():
_ctrls.time_label.text = ''
func _on_gut_start_script(script_obj):
next_script(script_obj.get_full_name(), script_obj.tests.size())
func _on_gut_end_script():
pass
func _on_gut_start_test(test_name):
next_test(test_name)
func _on_gut_end_test():
pass
func _on_gut_start_pause():
pause_before_teardown()
func _on_gut_end_pause():
pass
# ------------------
# Private
# ------------------
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
# ------------------
# Public
# ------------------
func set_num_scripts(val):
_ctrls.prog_script.value = 0
_ctrls.prog_script.max_value = val
func next_script(path, num_tests):
_ctrls.prog_script.value += 1
_ctrls.prog_test.value = 0
_ctrls.prog_test.max_value = num_tests
_ctrls.path_dir.text = path.get_base_dir()
_ctrls.path_file.text = path.get_file()
func next_test(test_name):
_ctrls.prog_test.value += 1
func pause_before_teardown():
_ctrls.btn_continue.visible = true
func set_gut(g):
_gut = g
g.start_run.connect(_on_gut_start_run)
g.end_run.connect(_on_gut_end_run)
g.start_script.connect(_on_gut_start_script)
g.end_script.connect(_on_gut_end_script)
g.start_test.connect(_on_gut_start_test)
g.end_test.connect(_on_gut_end_test)
g.start_pause_before_teardown.connect(_on_gut_start_pause)
g.end_pause_before_teardown.connect(_on_gut_end_pause)
func get_textbox():
return _ctrls.rtl
func set_elapsed_time(t):
_ctrls.time_label.text = str(t, 's')
func set_bg_color(c):
_ctrls.rtl_bg.color = c
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
var _large_handler = null
var _min_handler = null
var gut = null :
set(val):
gut = val
_set_gut(val)
func _ready():
if Engine.editor_hint:
return
_current_script.text = ""
_pre_maximize_rect = get_rect()
_hide_scripts()
_update_controls()
_nav.current_script.set_text("No scripts available")
set_title()
clear_summary()
_titlebar.time.set_text("t: 0.0")
_extras.visible = false
update()
set_font_size(_font_size)
set_font("CourierPrime")
_user_files.set_position(Vector2(10, 30))
func elapsed_time_as_str():
return str("%.1f" % (_time / 1000.0), "s")
_large_handler = GuiHandler.new($Large)
_min_handler = GuiHandler.new($Min)
$Min.visible = false
$Large.visible = !$Min.visible
func _process(_delta):
if _is_running:
_time = Time.get_ticks_msec() - _start_time
_titlebar.time.set_text(str("t: ", elapsed_time_as_str()))
if(gut != null and gut.is_running()):
_large_handler.set_elapsed_time(gut.get_elapsed_time())
_min_handler.set_elapsed_time(gut.get_elapsed_time())
func _set_gut(val):
_large_handler.set_gut(val)
_min_handler.set_gut(val)
func _draw(): # needs get_size()
# Draw the lines in the corner to show where you can
# drag to resize the dialog
var grab_margin = 3
var line_space = 3
var grab_line_color = Color(.4, .4, .4)
if _resize_handle.visible:
for i in range(1, 10):
var x = size - Vector2(i * line_space, grab_margin)
var y = size - Vector2(grab_margin, i * line_space)
draw_line(x,y,grab_line_color,1)
func _on_Maximize_draw():
# draw the maximize square thing.
var btn = $VBox/TitleBar/HBox/Maximize
btn.set_text("")
var w = btn.get_size().x
var h = btn.get_size().y
btn.draw_rect(Rect2(0, 2, w, h - 2), Color(0, 0, 0, 1))
btn.draw_rect(Rect2(2, 6, w - 4, h - 8), Color(1, 1, 1, 1))
func _on_ShowExtras_draw():
var btn = $VBox/BottomPanel/VBox/HBox/Continue/ShowExtras
btn.set_text("")
var start_x = 20
var start_y = 15
var pad = 5
var color = Color(.1, .1, .1, 1)
var width = 2
for i in range(3):
var y = start_y + pad * i
btn.draw_line(
Vector2(start_x, y), Vector2(btn.get_size().x - start_x, y), color, width, true
)
# ####################
# GUI Events
# ####################
func _on_Run_pressed():
_run_mode()
emit_signal("run_script", get_selected_index())
func _on_CurrentScript_pressed():
_toggle_scripts()
func _on_Previous_pressed():
_select_script(get_selected_index() - 1)
func _on_Next_pressed():
_select_script(get_selected_index() + 1)
func _on_LogLevelSlider_value_changed(_value):
emit_signal("log_level_changed", _log_level_slider.value)
func _on_Continue_pressed():
_continue_button.disabled = true
emit_signal("end_pause")
func _on_IgnorePause_pressed():
var checked = _ignore_pauses.is_pressed()
emit_signal("ignore_pause", checked)
if checked:
emit_signal("end_pause")
_continue_button.disabled = true
func _on_RunSingleScript_pressed():
_run_mode()
emit_signal("run_single_script", get_selected_index())
func _on_ScriptsList_item_selected(index):
var tmr = $ScriptsList/DoubleClickTimer
if !tmr.is_stopped():
_run_mode()
emit_signal("run_single_script", get_selected_index())
tmr.stop()
else:
tmr.start()
_select_script(index)
func _on_TitleBar_mouse_entered():
_mouse.in_title = true
func _on_TitleBar_mouse_exited():
_mouse.in_title = false
func _input(event):
if event is InputEventMouseButton:
if event.button_index == 1:
_mouse.down = event.pressed
if _mouse.down:
_mouse.down_pos = event.position
if _mouse.in_title:
if event is InputEventMouseMotion and _mouse.down:
set_position(get_position() + (event.position - _mouse.down_pos))
_mouse.down_pos = event.position
_pre_maximize_rect = get_rect()
if _mouse.in_handle:
if event is InputEventMouseMotion and _mouse.down:
var new_size = size + event.position - _mouse.down_pos
var new_mouse_down_pos = event.position
size = new_size
_mouse.down_pos = new_mouse_down_pos
_pre_maximize_rect = get_rect()
func _on_ResizeHandle_mouse_entered():
_mouse.in_handle = true
func _on_ResizeHandle_mouse_exited():
_mouse.in_handle = false
func _on_RichTextLabel_gui_input(ev):
pass
# leaving this b/c it is wired up and might have to send
# more signals through
func _on_Copy_pressed():
OS.clipboard = _text_box.text
func _on_ShowExtras_toggled(button_pressed):
_extras.visible = button_pressed
func _on_Maximize_pressed():
if get_rect() == _pre_maximize_rect:
compact_mode(false)
maximize()
else:
compact_mode(false)
size = _pre_maximize_rect.size
position = _pre_maximize_rect.position
func _on_Minimize_pressed():
compact_mode(!_compact_mode)
func _on_Minimize_draw():
# draw the maximize square thing.
var btn = $VBox/TitleBar/HBox/Minimize
btn.set_text("")
var w = btn.get_size().x
var h = btn.get_size().y
btn.draw_rect(Rect2(0, h - 3, w, 3), Color(0, 0, 0, 1))
func _on_UserFiles_pressed():
_user_files.show_open()
# ####################
# Private
# ####################
func _run_mode(is_running = true):
if is_running:
_start_time = Time.get_ticks_msec()
_time = 0.0
clear_summary()
_is_running = is_running
_hide_scripts()
_nav.prev.disabled = is_running
_nav.next.disabled = is_running
_nav.run.disabled = is_running
_nav.current_script.disabled = is_running
_nav.run_single.disabled = is_running
func _select_script(index):
var text = _script_list.get_item_text(index)
var max_len = 50
if text.length() > max_len:
text = "..." + text.right(text.length() - (max_len - 5))
_nav.current_script.set_text(text)
_script_list.select(index)
_update_controls()
func _toggle_scripts():
if _script_list.visible:
_hide_scripts()
else:
_show_scripts()
func _show_scripts():
_script_list.show()
func _hide_scripts():
_script_list.hide()
func _update_controls():
var is_empty = _script_list.get_selected_items().size() == 0
if is_empty:
_nav.next.disabled = true
_nav.prev.disabled = true
else:
var index = get_selected_index()
_nav.prev.disabled = index <= 0
_nav.next.disabled = index >= _script_list.get_item_count() - 1
_nav.run.disabled = is_empty
_nav.current_script.disabled = is_empty
_nav.run_single.disabled = is_empty
func _update_summary():
if !_summary:
return
var total = _summary.fail_count + _summary.pass_count
_summary.control.visible = !total == 0
_summary.asserts.text = str("Failures ", _summary.fail_count, "/", total)
# ####################
# Public
# ####################
func run_mode(is_running = true):
_run_mode(is_running)
func set_scripts(scripts):
_script_list.clear()
for i in range(scripts.size()):
_script_list.add_item(scripts[i])
_select_script(0)
_update_controls()
func select_script(index):
_select_script(index)
func get_selected_index():
return _script_list.get_selected_items()[0]
func get_log_level():
return _log_level_slider.value
func set_log_level(value):
var new_value = value
if new_value == null:
new_value = 0
# !! For some reason, _log_level_slider was null, but this wasn't, so
# here's another hardcoded node path.
$VBox/BottomPanel/VBox/HBox2/LogLevelSlider.value = new_value
func set_ignore_pause(should):
_ignore_pauses.button_pressed = should
func get_ignore_pause():
return _ignore_pauses.pressed
func get_text_box():
# due to some timing issue, this cannot return _text_box but can return
# this.
return $VBox/TextDisplay/RichTextLabel
func end_run():
_run_mode(false)
_update_controls()
func set_progress_script_max(value):
var max_val = max(value, 1)
_progress.script.set_max(max_val)
_progress.script_xy.set_text(str("0/", max_val))
func set_progress_script_value(value):
_progress.script.set_value(value)
var txt = str(value, "/", _progress.test.get_max())
_progress.script_xy.set_text(txt)
func set_progress_test_max(value):
var max_val = max(value, 1)
_progress.test.set_max(max_val)
_progress.test_xy.set_text(str("0/", max_val))
func set_progress_test_value(value):
_progress.test.set_value(value)
var txt = str(value, "/", _progress.test.get_max())
_progress.test_xy.set_text(txt)
func clear_progress():
_progress.test.set_value(0)
_progress.script.set_value(0)
func pause():
_continue_button.disabled = false
func set_title(title = null):
if title == null:
_titlebar.label.set_text(DEFAULT_TITLE)
else:
_titlebar.label.set_text(title)
func add_passing(amount = 1):
if !_summary:
return
_summary.pass_count += amount
_update_summary()
func add_failing(amount = 1):
if !_summary:
return
_summary.fail_count += amount
_update_summary()
func clear_summary():
_summary.fail_count = 0
_summary.pass_count = 0
_update_summary()
func maximize():
if is_inside_tree():
var vp_size_offset = get_tree().root.get_viewport().get_visible_rect().size
size = vp_size_offset / get_scale()
set_position(Vector2(0, 0))
func clear_text():
_text_box.text = ""
func scroll_to_bottom():
pass
#_text_box.set_caret_line(_gui.get_text_box().get_line_count())
func _set_font_size_for_rtl(rtl, new_size):
if rtl.get("custom_fonts/normal_font") != null:
rtl.get("custom_fonts/bold_italics_font").size = new_size
rtl.get("custom_fonts/bold_font").size = new_size
rtl.get("custom_fonts/italics_font").size = new_size
rtl.get("custom_fonts/normal_font").size = new_size
func _set_fonts_for_rtl(rtl, base_font_name):
pass
func get_textbox():
return _large_handler.get_textbox()
func set_font_size(new_size):
_font_size = new_size
_set_font_size_for_rtl(_text_box, new_size)
_set_font_size_for_rtl(_user_files.get_rich_text_label(), new_size)
var rtl = _large_handler.get_textbox()
if(rtl.get('custom_fonts/normal_font') != null):
rtl.get('custom_fonts/bold_italics_font').size = new_size
rtl.get('custom_fonts/bold_font').size = new_size
rtl.get('custom_fonts/italics_font').size = new_size
rtl.get('custom_fonts/normal_font').size = new_size
func set_font(font_name):
pass
#_set_all_fonts_in_rtl(_large_handler.get_textbox(), font_name)
# Needs rework for 4.0, DynamicFont DNE
func _set_font(rtl, font_name, custom_name):
if font_name == null:
rtl.set("custom_fonts/" + custom_name, null)
pass
# if(font_name == null):
# rtl.set('custom_fonts/' + custom_name, null)
# else:
# var dyn_font = DynamicFont.new()
# var font_data = DynamicFontData.new()
# font_data.font_path = 'res://addons/gut/fonts/' + font_name + '.ttf'
# font_data.antialiased = true
# dyn_font.font_data = font_data
# rtl.set('custom_fonts/' + custom_name, dyn_font)
func _set_all_fonts_in_rtl(rtl, base_name):
if(base_name == 'Default'):
_set_font(rtl, null, 'normal_font')
_set_font(rtl, null, 'bold_font')
_set_font(rtl, null, 'italics_font')
_set_font(rtl, null, 'bold_italics_font')
else:
var dyn_font = preload("res://addons/godot_xterm/themes/fonts/regular.tres").duplicate()
rtl.set("custom_fonts/" + custom_name, dyn_font)
func _set_all_fonts_in_ftl(ftl, base_name):
if base_name == "Default":
_set_font(ftl, null, "normal_font")
_set_font(ftl, null, "bold_font")
_set_font(ftl, null, "italics_font")
_set_font(ftl, null, "bold_italics_font")
else:
_set_font(ftl, base_name + "-Regular", "normal_font")
_set_font(ftl, base_name + "-Bold", "bold_font")
_set_font(ftl, base_name + "-Italic", "italics_font")
_set_font(ftl, base_name + "-BoldItalic", "bold_italics_font")
set_font_size(_font_size)
func set_font(base_name):
_set_all_fonts_in_ftl(_text_box, base_name)
_set_all_fonts_in_ftl(_user_files.get_rich_text_label(), base_name)
_set_font(rtl, base_name + '-Regular', 'normal_font')
_set_font(rtl, base_name + '-Bold', 'bold_font')
_set_font(rtl, base_name + '-Italic', 'italics_font')
_set_font(rtl, base_name + '-BoldItalic', 'bold_italics_font')
func set_default_font_color(color):
_text_box.set("custom_colors/default_color", color)
_large_handler.get_textbox().set('custom_colors/default_color', color)
func set_background_color(color):
_text_box_container.color = color
func get_waiting_label():
return $VBox/TextDisplay/WaitingLabel
func compact_mode(should):
if _compact_mode == should:
return
_compact_mode = should
_text_box_container.visible = !should
_nav.container.visible = !should
_log_level_slider.visible = !should
$VBox/BottomPanel/VBox/HBox/Continue/ShowExtras.visible = !should
_titlebar.label.visible = !should
_resize_handle.visible = !should
_current_script.visible = !should
_title_replacement.visible = should
if should:
minimum_size = min_sizes.compact
size = minimum_size
else:
minimum_size = min_sizes.full
size = min_sizes.full
goto_bottom_right_corner()
func set_script_path(text):
_current_script.text = text
func goto_bottom_right_corner():
position = get_tree().root.get_viewport().get_visible_rect().size - size
_large_handler.set_bg_color(color)

View file

@ -1,614 +1,375 @@
[gd_scene load_steps=7 format=2]
[gd_scene load_steps=4 format=3 uid="uid://m28heqtswbuq"]
[ext_resource path="res://addons/gut/GutScene.gd" type="Script" id=1]
[ext_resource path="res://addons/gut/UserFileViewer.tscn" type="PackedScene" id=6]
[ext_resource type="Script" path="res://addons/gut/GutScene.gd" id="1_b4m8y"]
[ext_resource type="Theme" uid="uid://cstkhwkpajvqu" path="res://addons/gut/gui/GutSceneTheme.tres" id="1_s37wl"]
[ext_resource type="FontFile" uid="uid://bnh0lslf4yh87" path="res://addons/gut/fonts/CourierPrime-Regular.ttf" id="3_qvb8f"]
[sub_resource type="StyleBoxFlat" id=1]
bg_color = Color( 0.192157, 0.192157, 0.227451, 1 )
corner_radius_top_left = 10
corner_radius_top_right = 10
[node name="GutScene" type="Node2D"]
script = ExtResource("1_b4m8y")
[sub_resource type="StyleBoxFlat" id=2]
bg_color = Color( 1, 1, 1, 1 )
border_color = Color( 0, 0, 0, 1 )
corner_radius_top_left = 5
corner_radius_top_right = 5
[node name="Large" type="Panel" parent="."]
offset_right = 717.0
offset_bottom = 388.0
theme = ExtResource("1_s37wl")
[sub_resource type="Theme" id=3]
resource_local_to_scene = true
Panel/styles/panel = SubResource( 2 )
Panel/styles/panelf = null
Panel/styles/panelnc = null
[node name="MainBox" type="VBoxContainer" parent="Large"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_layout_mode = 1
[sub_resource type="StyleBoxFlat" id=8]
bg_color = Color( 0.192157, 0.192157, 0.227451, 1 )
corner_radius_top_left = 20
corner_radius_top_right = 20
[node name="TitleBar" type="Panel" parent="Large/MainBox"]
custom_minimum_size = Vector2(0, 25)
offset_right = 717.0
offset_bottom = 25.0
[node name="Gut" type="Panel"]
offset_right = 740.0
[node name="TitleBox" type="HBoxContainer" parent="Large/MainBox/TitleBar"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = 2.0
offset_bottom = 3.0
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_layout_mode = 1
[node name="Spacer1" type="CenterContainer" parent="Large/MainBox/TitleBar/TitleBox"]
offset_right = 285.0
offset_bottom = 26.0
size_flags_horizontal = 3
[node name="Title" type="Label" parent="Large/MainBox/TitleBar/TitleBox"]
offset_left = 289.0
offset_top = 3.0
offset_right = 334.0
offset_bottom = 23.0
text = "Title"
[node name="Spacer2" type="CenterContainer" parent="Large/MainBox/TitleBar/TitleBox"]
offset_left = 338.0
offset_right = 623.0
offset_bottom = 26.0
size_flags_horizontal = 3
[node name="TimeLabel" type="Label" parent="Large/MainBox/TitleBar/TitleBox"]
custom_minimum_size = Vector2(90, 0)
offset_left = 627.0
offset_top = 3.0
offset_right = 717.0
offset_bottom = 23.0
text = "999.999s"
[node name="HBoxContainer" type="HBoxContainer" parent="Large/MainBox"]
offset_top = 29.0
offset_right = 717.0
offset_bottom = 379.0
size_flags_vertical = 3
[node name="VBoxContainer" type="VBoxContainer" parent="Large/MainBox/HBoxContainer"]
offset_right = 717.0
offset_bottom = 350.0
size_flags_horizontal = 3
[node name="OutputBG" type="ColorRect" parent="Large/MainBox/HBoxContainer/VBoxContainer"]
offset_right = 717.0
offset_bottom = 300.0
minimum_size = Vector2( 740, 300 )
custom_styles/panel = SubResource( 1 )
script = ExtResource( 1 )
grow_horizontal = 2
grow_vertical = 2
size_flags_vertical = 3
color = Color(0.0745098, 0.0705882, 0.0784314, 1)
metadata/_edit_layout_mode = 1
[node name="UserFileViewer" parent="." instance=ExtResource( 6 )]
offset_top = 388.0
offset_bottom = 818.0
[node name="VBox" type="VBoxContainer" parent="."]
[node name="HBoxContainer" type="HBoxContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/OutputBG"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
grow_horizontal = 2
grow_vertical = 2
[node name="TitleBar" type="Panel" parent="VBox"]
offset_right = 740.0
offset_bottom = 30.0
minimum_size = Vector2( 0, 30 )
theme = SubResource( 3 )
__meta__ = {
"_edit_group_": true,
"_edit_use_anchors_": false
}
[node name="S2" type="CenterContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer"]
custom_minimum_size = Vector2(5, 0)
offset_right = 5.0
offset_bottom = 300.0
[node name="HBox" type="HBoxContainer" parent="VBox/TitleBar"]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Summary" type="Control" parent="VBox/TitleBar/HBox"]
offset_right = 110.0
offset_bottom = 30.0
minimum_size = Vector2( 110, 0 )
mouse_filter = 2
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Passing" type="Label" parent="VBox/TitleBar/HBox/Summary"]
visible = false
offset_left = 5.0
offset_top = 7.0
offset_right = 45.0
offset_bottom = 21.0
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "0"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Failing" type="Label" parent="VBox/TitleBar/HBox/Summary"]
visible = false
offset_left = 100.0
offset_top = 7.0
offset_right = 140.0
offset_bottom = 21.0
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "0"
align = 1
valign = 1
[node name="AssertCount" type="Label" parent="VBox/TitleBar/HBox/Summary"]
offset_left = 5.0
offset_top = 7.0
offset_right = 165.0
offset_bottom = 21.0
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "Assert count"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TitleReplacement" type="CenterContainer" parent="VBox/TitleBar/HBox"]
visible = false
offset_left = 114.0
offset_right = 352.0
offset_bottom = 30.0
minimum_size = Vector2( 5, 0 )
mouse_filter = 2
[node name="Output" type="RichTextLabel" parent="Large/MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer"]
offset_left = 9.0
offset_right = 708.0
offset_bottom = 300.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Title" type="Label" parent="VBox/TitleBar/HBox"]
offset_left = 114.0
offset_right = 598.0
offset_bottom = 30.0
size_flags_horizontal = 3
size_flags_vertical = 7
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "Gut"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Time" type="Label" parent="VBox/TitleBar/HBox"]
offset_left = 602.0
offset_top = 8.0
offset_right = 654.0
offset_bottom = 22.0
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "9999.99"
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CC" type="CenterContainer" parent="VBox/TitleBar/HBox"]
offset_left = 658.0
offset_right = 663.0
offset_bottom = 30.0
minimum_size = Vector2( 5, 0 )
mouse_filter = 2
[node name="Minimize" type="Button" parent="VBox/TitleBar/HBox"]
offset_left = 667.0
offset_right = 697.0
offset_bottom = 30.0
minimum_size = Vector2( 30, 0 )
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "N"
flat = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Maximize" type="Button" parent="VBox/TitleBar/HBox"]
offset_left = 701.0
offset_right = 731.0
offset_bottom = 30.0
minimum_size = Vector2( 30, 0 )
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "X"
flat = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CC2" type="CenterContainer" parent="VBox/TitleBar/HBox"]
offset_left = 735.0
offset_right = 740.0
offset_bottom = 30.0
minimum_size = Vector2( 5, 0 )
mouse_filter = 2
[node name="TextDisplay" type="ColorRect" parent="VBox"]
offset_top = 34.0
offset_right = 740.0
offset_bottom = 176.0
size_flags_horizontal = 3
size_flags_vertical = 3
color = Color( 0, 0, 0, 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="RichTextLabel" type="RichTextLabel" parent="VBox/TextDisplay"]
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 10.0
minimum_size = Vector2( 0, 116 )
focus_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
bbcode_enabled = true
scroll_following = true
selection_enabled = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="WaitingLabel" type="RichTextLabel" parent="VBox/TextDisplay"]
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -25.0
bbcode_enabled = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="BottomPanel" type="ColorRect" parent="VBox"]
offset_top = 180.0
offset_right = 740.0
[node name="S1" type="CenterContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer"]
custom_minimum_size = Vector2(5, 0)
offset_left = 712.0
offset_right = 717.0
offset_bottom = 300.0
minimum_size = Vector2( 0, 120 )
size_flags_horizontal = 9
size_flags_vertical = 9
color = Color( 1, 1, 1, 0 )
[node name="VBox" type="VBoxContainer" parent="VBox/BottomPanel"]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ControlBox" type="HBoxContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer"]
offset_top = 304.0
offset_right = 717.0
offset_bottom = 350.0
[node name="HBox" type="HBoxContainer" parent="VBox/BottomPanel/VBox"]
offset_right = 740.0
offset_bottom = 80.0
size_flags_horizontal = 3
[node name="CC1" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox"]
[node name="S1" type="CenterContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox"]
custom_minimum_size = Vector2(5, 0)
offset_right = 5.0
offset_bottom = 80.0
minimum_size = Vector2( 5, 0 )
offset_bottom = 46.0
[node name="Progress" type="VBoxContainer" parent="VBox/BottomPanel/VBox/HBox"]
[node name="ProgressBars" type="VBoxContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox"]
offset_left = 9.0
offset_right = 179.0
offset_bottom = 80.0
minimum_size = Vector2( 170, 0 )
alignment = 1
offset_right = 176.0
offset_bottom = 46.0
[node name="TestProgress" type="ProgressBar" parent="VBox/BottomPanel/VBox/HBox/Progress"]
offset_top = 11.0
offset_right = 100.0
offset_bottom = 36.0
minimum_size = Vector2( 100, 25 )
tooltip_text = "Test progress for the current script."
size_flags_horizontal = 0
step = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TestBox" type="HBoxContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars"]
offset_right = 167.0
offset_bottom = 21.0
[node name="Label" type="Label" parent="VBox/BottomPanel/VBox/HBox/Progress/TestProgress"]
offset_left = 107.5
offset_top = 3.0
offset_right = 172.5
offset_bottom = 18.0
[node name="Label" type="Label" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/TestBox"]
custom_minimum_size = Vector2(60, 0)
offset_right = 60.0
offset_bottom = 20.0
text = "Tests"
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="xy" type="Label" parent="VBox/BottomPanel/VBox/HBox/Progress/TestProgress"]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
text = "0/0"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ProgressTest" type="ProgressBar" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/TestBox"]
custom_minimum_size = Vector2(100, 0)
offset_left = 64.0
offset_right = 164.0
offset_bottom = 21.0
value = 25.0
[node name="ScriptProgress" type="ProgressBar" parent="VBox/BottomPanel/VBox/HBox/Progress"]
offset_top = 40.0
offset_right = 100.0
offset_bottom = 65.0
minimum_size = Vector2( 100, 25 )
tooltip_text = "Overall progress of executing tests."
size_flags_horizontal = 0
step = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ScriptBox" type="HBoxContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars"]
offset_top = 25.0
offset_right = 167.0
offset_bottom = 46.0
[node name="Label" type="Label" parent="VBox/BottomPanel/VBox/HBox/Progress/ScriptProgress"]
offset_left = 107.0
offset_top = 3.5
offset_right = 172.0
offset_bottom = 18.5
[node name="Label" type="Label" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/ScriptBox"]
custom_minimum_size = Vector2(60, 0)
offset_right = 63.0
offset_bottom = 20.0
text = "Scripts"
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="xy" type="Label" parent="VBox/BottomPanel/VBox/HBox/Progress/ScriptProgress"]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
text = "0/0"
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ProgressScript" type="ProgressBar" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/ScriptBox"]
custom_minimum_size = Vector2(100, 0)
offset_left = 67.0
offset_right = 167.0
offset_bottom = 21.0
value = 75.0
[node name="CenterContainer" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox/Progress"]
offset_top = 69.0
offset_right = 170.0
offset_bottom = 69.0
[node name="CC2" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox"]
offset_left = 183.0
offset_right = 226.0
offset_bottom = 80.0
minimum_size = Vector2( 5, 0 )
size_flags_horizontal = 3
[node name="Node3D" type="Panel" parent="VBox/BottomPanel/VBox/HBox"]
self_modulate = Color( 1, 1, 1, 0 )
offset_left = 230.0
offset_right = 580.0
offset_bottom = 80.0
minimum_size = Vector2( 350, 80 )
__meta__ = {
"_edit_group_": true,
"_edit_use_anchors_": false
}
[node name="VBox" type="VBoxContainer" parent="VBox/BottomPanel/VBox/HBox/Node3D"]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CurrentScript" type="Button" parent="VBox/BottomPanel/VBox/HBox/Node3D/VBox"]
offset_right = 350.0
offset_bottom = 38.0
tooltip_text = "Select a script to run. You can run just this script, or this script and all scripts after using the run buttons."
size_flags_horizontal = 3
size_flags_vertical = 3
text = "res://test/unit/test_gut.gd"
clip_text = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="HBox" type="HBoxContainer" parent="VBox/BottomPanel/VBox/HBox/Node3D/VBox"]
offset_top = 42.0
offset_right = 350.0
offset_bottom = 80.0
size_flags_horizontal = 3
[node name="PathDisplay" type="VBoxContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox"]
offset_left = 180.0
offset_right = 385.0
offset_bottom = 46.0
size_flags_vertical = 3
[node name="Previous" type="Button" parent="VBox/BottomPanel/VBox/HBox/Node3D/VBox/HBox"]
offset_right = 84.0
offset_bottom = 38.0
tooltip_text = "Previous script in the list."
size_flags_horizontal = 3
size_flags_vertical = 3
text = "|<"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Path" type="Label" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay"]
offset_right = 205.0
offset_bottom = 16.0
theme_override_fonts/font = ExtResource("3_qvb8f")
theme_override_font_sizes/font_size = 11
text = "res://test/integration/whatever"
[node name="Next" type="Button" parent="VBox/BottomPanel/VBox/HBox/Node3D/VBox/HBox"]
offset_left = 88.0
offset_right = 173.0
offset_bottom = 38.0
tooltip_text = "Next script in the list.
"
size_flags_horizontal = 3
size_flags_vertical = 3
text = ">|"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="HBoxContainer" type="HBoxContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay"]
offset_top = 20.0
offset_right = 205.0
offset_bottom = 36.0
[node name="Run" type="Button" parent="VBox/BottomPanel/VBox/HBox/Node3D/VBox/HBox"]
offset_left = 177.0
offset_right = 261.0
offset_bottom = 38.0
tooltip_text = "Run the currently selected item and all after it."
size_flags_horizontal = 3
size_flags_vertical = 3
text = ">"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="RunSingleScript" type="Button" parent="VBox/BottomPanel/VBox/HBox/Node3D/VBox/HBox"]
offset_left = 265.0
offset_right = 350.0
offset_bottom = 38.0
tooltip_text = "Run the currently selected item.
If the selected item has Inner Test Classes
then they will all be run. If the selected item
is an Inner Test Class then only it will be run."
size_flags_horizontal = 3
size_flags_vertical = 3
text = "> (1)"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CC3" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox"]
offset_left = 584.0
offset_right = 627.0
offset_bottom = 80.0
minimum_size = Vector2( 5, 0 )
size_flags_horizontal = 3
[node name="Continue" type="VBoxContainer" parent="VBox/BottomPanel/VBox/HBox"]
self_modulate = Color( 1, 1, 1, 0 )
offset_left = 631.0
offset_right = 731.0
offset_bottom = 80.0
alignment = 1
[node name="ShowExtras" type="Button" parent="VBox/BottomPanel/VBox/HBox/Continue"]
offset_right = 50.0
offset_bottom = 35.0
minimum_size = Vector2( 50, 35 )
pivot_offset = Vector2( 35, 20 )
tooltip_text = "Show/hide additional options."
size_flags_horizontal = 0
toggle_mode = true
text = "_"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Continue" type="Button" parent="VBox/BottomPanel/VBox/HBox/Continue"]
offset_top = 39.0
offset_right = 100.0
offset_bottom = 79.0
minimum_size = Vector2( 100, 40 )
tooltip_text = "When a pause_before_teardown is encountered this button will be enabled and must be pressed to continue running tests."
disabled = true
text = "Continue"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CC4" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox"]
offset_left = 735.0
offset_right = 740.0
offset_bottom = 80.0
minimum_size = Vector2( 5, 0 )
[node name="HBox2" type="HBoxContainer" parent="VBox/BottomPanel/VBox"]
offset_top = 84.0
offset_right = 740.0
offset_bottom = 114.0
[node name="CC" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox2"]
[node name="S3" type="CenterContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay/HBoxContainer"]
custom_minimum_size = Vector2(5, 0)
offset_right = 5.0
offset_bottom = 30.0
minimum_size = Vector2( 5, 0 )
offset_bottom = 16.0
[node name="LogLevelSlider" type="HSlider" parent="VBox/BottomPanel/VBox/HBox2"]
[node name="File" type="Label" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay/HBoxContainer"]
offset_left = 9.0
offset_right = 109.0
offset_bottom = 30.0
minimum_size = Vector2( 100, 30 )
size_flags_vertical = 3
max_value = 2.0
tick_count = 3
ticks_on_borders = true
__meta__ = {
"_edit_use_anchors_": false
}
offset_right = 128.0
offset_bottom = 16.0
theme_override_fonts/font = ExtResource("3_qvb8f")
theme_override_font_sizes/font_size = 11
text = "test_this_thing.gd"
[node name="Label" type="Label" parent="VBox/BottomPanel/VBox/HBox2/LogLevelSlider"]
offset_left = 4.0
offset_top = -17.0
offset_right = 85.0
offset_bottom = 7.0
text = "Log Level"
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CenterContainer" type="CenterContainer" parent="VBox/BottomPanel/VBox/HBox2"]
offset_left = 113.0
offset_right = 163.0
offset_bottom = 30.0
minimum_size = Vector2( 50, 0 )
[node name="CurrentScriptLabel" type="Label" parent="VBox/BottomPanel/VBox/HBox2"]
offset_left = 167.0
offset_top = 8.0
offset_right = 740.0
offset_bottom = 22.0
[node name="Spacer1" type="CenterContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox"]
offset_left = 389.0
offset_right = 624.0
offset_bottom = 46.0
size_flags_horizontal = 3
size_flags_vertical = 6
text = "res://test/unit/test_something.gd"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ScriptsList" type="ItemList" parent="."]
visible = false
anchor_bottom = 1.0
offset_left = 179.0
offset_top = 40.0
offset_right = 619.0
offset_bottom = -110.0
allow_reselect = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Continue" type="Button" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox"]
offset_left = 628.0
offset_top = 10.0
offset_right = 708.0
offset_bottom = 35.0
size_flags_vertical = 4
text = "Continue
"
[node name="DoubleClickTimer" type="Timer" parent="ScriptsList"]
wait_time = 0.3
one_shot = true
[node name="S3" type="CenterContainer" parent="Large/MainBox/HBoxContainer/VBoxContainer/ControlBox"]
custom_minimum_size = Vector2(5, 0)
offset_left = 712.0
offset_right = 717.0
offset_bottom = 46.0
[node name="ExtraOptions" type="Panel" parent="."]
visible = false
anchor_left = 1.0
anchor_top = 1.0
[node name="BottomPad" type="CenterContainer" parent="Large/MainBox"]
custom_minimum_size = Vector2(0, 5)
offset_top = 383.0
offset_right = 717.0
offset_bottom = 388.0
[node name="Min" type="Panel" parent="."]
clip_contents = true
offset_left = 456.0
offset_top = 396.0
offset_right = 719.0
offset_bottom = 515.0
theme = ExtResource("1_s37wl")
metadata/_edit_group_ = true
[node name="MainBox" type="VBoxContainer" parent="Min"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -212.0
offset_top = -260.0
offset_right = -2.0
offset_bottom = -106.0
custom_styles/panel = SubResource( 8 )
__meta__ = {
"_edit_use_anchors_": false
}
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_layout_mode = 1
[node name="IgnorePause" type="CheckBox" parent="ExtraOptions"]
offset_left = 17.5
offset_top = 4.5
offset_right = 162.5
offset_bottom = 29.5
scale = Vector2( 1.2, 1.2 )
tooltip_text = "Ignore all calls to pause_before_teardown."
text = "Ignore Pauses"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TitleBar" type="Panel" parent="Min/MainBox"]
custom_minimum_size = Vector2(0, 25)
offset_right = 266.0
offset_bottom = 25.0
[node name="Copy" type="Button" parent="ExtraOptions"]
offset_left = 15.0
offset_top = 40.0
offset_right = 195.0
offset_bottom = 80.0
tooltip_text = "Copy all output to the clipboard."
text = "Copy to Clipboard"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="UserFiles" type="Button" parent="ExtraOptions"]
offset_left = 15.0
offset_top = 90.0
offset_right = 195.0
offset_bottom = 130.0
tooltip_text = "Copy all output to the clipboard."
text = "View User Files"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ResizeHandle" type="Control" parent="."]
anchor_left = 1.0
anchor_top = 1.0
[node name="TitleBox" type="HBoxContainer" parent="Min/MainBox/TitleBar"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -40.0
offset_top = -40.0
__meta__ = {
"_edit_use_anchors_": false
}
offset_top = 2.0
offset_bottom = 3.0
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_layout_mode = 1
[connection signal="mouse_entered" from="VBox/TitleBar" to="." method="_on_TitleBar_mouse_entered"]
[connection signal="mouse_exited" from="VBox/TitleBar" to="." method="_on_TitleBar_mouse_exited"]
[connection signal="draw" from="VBox/TitleBar/HBox/Minimize" to="." method="_on_Minimize_draw"]
[connection signal="pressed" from="VBox/TitleBar/HBox/Minimize" to="." method="_on_Minimize_pressed"]
[connection signal="draw" from="VBox/TitleBar/HBox/Maximize" to="." method="_on_Maximize_draw"]
[connection signal="pressed" from="VBox/TitleBar/HBox/Maximize" to="." method="_on_Maximize_pressed"]
[connection signal="gui_input" from="VBox/TextDisplay/RichTextLabel" to="." method="_on_RichTextLabel_gui_input"]
[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Node3D/VBox/CurrentScript" to="." method="_on_CurrentScript_pressed"]
[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Node3D/VBox/HBox/Previous" to="." method="_on_Previous_pressed"]
[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Node3D/VBox/HBox/Next" to="." method="_on_Next_pressed"]
[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Node3D/VBox/HBox/Run" to="." method="_on_Run_pressed"]
[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Node3D/VBox/HBox/RunSingleScript" to="." method="_on_RunSingleScript_pressed"]
[connection signal="draw" from="VBox/BottomPanel/VBox/HBox/Continue/ShowExtras" to="." method="_on_ShowExtras_draw"]
[connection signal="toggled" from="VBox/BottomPanel/VBox/HBox/Continue/ShowExtras" to="." method="_on_ShowExtras_toggled"]
[connection signal="pressed" from="VBox/BottomPanel/VBox/HBox/Continue/Continue" to="." method="_on_Continue_pressed"]
[connection signal="value_changed" from="VBox/BottomPanel/VBox/HBox2/LogLevelSlider" to="." method="_on_LogLevelSlider_value_changed"]
[connection signal="item_selected" from="ScriptsList" to="." method="_on_ScriptsList_item_selected"]
[connection signal="pressed" from="ExtraOptions/IgnorePause" to="." method="_on_IgnorePause_pressed"]
[connection signal="pressed" from="ExtraOptions/Copy" to="." method="_on_Copy_pressed"]
[connection signal="pressed" from="ExtraOptions/UserFiles" to="." method="_on_UserFiles_pressed"]
[connection signal="mouse_entered" from="ResizeHandle" to="." method="_on_ResizeHandle_mouse_entered"]
[connection signal="mouse_exited" from="ResizeHandle" to="." method="_on_ResizeHandle_mouse_exited"]
[node name="Spacer1" type="CenterContainer" parent="Min/MainBox/TitleBar/TitleBox"]
offset_right = 77.0
offset_bottom = 26.0
size_flags_horizontal = 3
[node name="Title" type="Label" parent="Min/MainBox/TitleBar/TitleBox"]
offset_left = 81.0
offset_top = 3.0
offset_right = 126.0
offset_bottom = 23.0
text = "Title"
[node name="Spacer2" type="CenterContainer" parent="Min/MainBox/TitleBar/TitleBox"]
offset_left = 130.0
offset_right = 208.0
offset_bottom = 26.0
size_flags_horizontal = 3
[node name="TimeLabel" type="Label" parent="Min/MainBox/TitleBar/TitleBox"]
offset_left = 212.0
offset_top = 3.0
offset_right = 266.0
offset_bottom = 23.0
text = "0.000s"
[node name="Body" type="HBoxContainer" parent="Min/MainBox"]
offset_top = 29.0
offset_right = 266.0
offset_bottom = 119.0
size_flags_vertical = 3
[node name="LeftMargin" type="CenterContainer" parent="Min/MainBox/Body"]
custom_minimum_size = Vector2(5, 0)
offset_right = 5.0
offset_bottom = 90.0
[node name="BodyRows" type="VBoxContainer" parent="Min/MainBox/Body"]
offset_left = 9.0
offset_right = 257.0
offset_bottom = 90.0
size_flags_horizontal = 3
[node name="ProgressBars" type="HBoxContainer" parent="Min/MainBox/Body/BodyRows"]
offset_right = 248.0
offset_bottom = 21.0
size_flags_horizontal = 3
[node name="HBoxContainer" type="HBoxContainer" parent="Min/MainBox/Body/BodyRows/ProgressBars"]
offset_right = 122.0
offset_bottom = 21.0
[node name="Label" type="Label" parent="Min/MainBox/Body/BodyRows/ProgressBars/HBoxContainer"]
offset_right = 18.0
offset_bottom = 20.0
text = "T:"
[node name="ProgressTest" type="ProgressBar" parent="Min/MainBox/Body/BodyRows/ProgressBars/HBoxContainer"]
custom_minimum_size = Vector2(100, 0)
offset_left = 22.0
offset_right = 122.0
offset_bottom = 21.0
value = 25.0
[node name="HBoxContainer2" type="HBoxContainer" parent="Min/MainBox/Body/BodyRows/ProgressBars"]
offset_left = 126.0
offset_right = 248.0
offset_bottom = 21.0
[node name="Label" type="Label" parent="Min/MainBox/Body/BodyRows/ProgressBars/HBoxContainer2"]
offset_right = 18.0
offset_bottom = 20.0
text = "S:"
[node name="ProgressScript" type="ProgressBar" parent="Min/MainBox/Body/BodyRows/ProgressBars/HBoxContainer2"]
custom_minimum_size = Vector2(100, 0)
offset_left = 22.0
offset_right = 122.0
offset_bottom = 21.0
value = 75.0
[node name="PathDisplay" type="VBoxContainer" parent="Min/MainBox/Body/BodyRows"]
offset_top = 25.0
offset_right = 248.0
offset_bottom = 61.0
size_flags_vertical = 3
[node name="Path" type="Label" parent="Min/MainBox/Body/BodyRows/PathDisplay"]
offset_right = 248.0
offset_bottom = 16.0
theme_override_fonts/font = ExtResource("3_qvb8f")
theme_override_font_sizes/font_size = 11
text = "res://test/integration/whatever"
[node name="HBoxContainer" type="HBoxContainer" parent="Min/MainBox/Body/BodyRows/PathDisplay"]
offset_top = 20.0
offset_right = 248.0
offset_bottom = 36.0
[node name="S3" type="CenterContainer" parent="Min/MainBox/Body/BodyRows/PathDisplay/HBoxContainer"]
custom_minimum_size = Vector2(5, 0)
offset_right = 5.0
offset_bottom = 16.0
[node name="File" type="Label" parent="Min/MainBox/Body/BodyRows/PathDisplay/HBoxContainer"]
offset_left = 9.0
offset_right = 128.0
offset_bottom = 16.0
theme_override_fonts/font = ExtResource("3_qvb8f")
theme_override_font_sizes/font_size = 11
text = "test_this_thing.gd"
[node name="Continue" type="Button" parent="Min/MainBox/Body/BodyRows"]
offset_top = 65.0
offset_right = 248.0
offset_bottom = 90.0
text = "Continue
"
[node name="RightMargin" type="CenterContainer" parent="Min/MainBox/Body"]
custom_minimum_size = Vector2(5, 0)
offset_left = 261.0
offset_right = 266.0
offset_bottom = 90.0

View file

@ -1,66 +1,51 @@
extends Window
@onready var rtl = $TextDisplay/RichTextLabel
var _has_opened_file = false
func _get_file_as_text(path):
var to_return = null
var f = File.new()
var result = f.open(path, f.READ)
if result == OK:
var f = FileAccess.open(path, FileAccess.READ)
if(f != null):
to_return = f.get_as_text()
f.close()
else:
to_return = str("ERROR: Could not open file. Error code ", result)
to_return = str('ERROR: Could not open file. Error code ', FileAccess.get_open_error())
return to_return
func _ready():
rtl.clear()
func _on_OpenFile_pressed():
$FileDialog.popup_centered()
func _on_FileDialog_file_selected(path):
show_file(path)
func _on_Close_pressed():
self.hide()
func show_file(path):
var text = _get_file_as_text(path)
if text == "":
text = "<Empty File>"
if(text == ''):
text = '<Empty File>'
rtl.set_text(text)
self.window_title = path
func show_open():
self.popup_centered()
$FileDialog.popup_centered()
func _on_FileDialog_popup_hide():
if rtl.text.length() == 0:
self.hide()
func get_rich_text_label():
return $TextDisplay/RichTextLabel
func _on_Home_pressed():
rtl.scroll_to_line(0)
func _on_End_pressed():
rtl.scroll_to_line(rtl.get_line_count() - 1)
rtl.scroll_to_line(rtl.get_line_count() -1)
func _on_Copy_pressed():
OS.clipboard = rtl.text
func _on_file_dialog_visibility_changed():
if rtl.text.length() == 0 and not $FileDialog.visible:
self.hide()

View file

@ -1,33 +1,14 @@
[gd_scene load_steps=2 format=2]
[gd_scene load_steps=2 format=3 uid="uid://bsm7wtt1gie4v"]
[ext_resource path="res://addons/gut/UserFileViewer.gd" type="Script" id=1]
[ext_resource type="Script" path="res://addons/gut/UserFileViewer.gd" id="1"]
[node name="UserFileViewer" type="Window"]
offset_top = 20.0
offset_right = 800.0
offset_bottom = 450.0
minimum_size = Vector2( 800, 180 )
exclusive = true
window_title = "View File"
resizable = true
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
script = ExtResource("1")
[node name="FileDialog" type="FileDialog" parent="."]
offset_right = 416.0
offset_bottom = 184.0
minimum_size = Vector2( 400, 140 )
scale = Vector2( 2, 2 )
exclusive = true
window_title = "Open a File"
resizable = true
mode = 0
access = 1
show_hidden_files = true
current_dir = "user://"
current_path = "user://"
__meta__ = {
"_edit_use_anchors_": false
}
@ -38,24 +19,18 @@ anchor_bottom = 1.0
offset_left = 8.0
offset_right = -10.0
offset_bottom = -65.0
color = Color( 0.2, 0.188235, 0.188235, 1 )
__meta__ = {
"_edit_use_anchors_": false
}
color = Color(0.2, 0.188235, 0.188235, 1)
[node name="RichTextLabel" type="RichTextLabel" parent="TextDisplay"]
anchor_right = 1.0
anchor_bottom = 1.0
focus_mode = 2
text = "In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying checked meaningful content. Lorem ipsum may be used before final copy is available, but it may also be used to temporarily replace copy in a process called greeking, which allows designers to consider form without the meaning of the text influencing the design.
text = "In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content. Lorem ipsum may be used before final copy is available, but it may also be used to temporarily replace copy in a process called greeking, which allows designers to consider form without the meaning of the text influencing the design.
Lorem ipsum is typically a corrupted version of De finibus bonorum et malorum, a first-century BCE text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical, improper Latin.
Versions of the Lorem ipsum text have been used in typesetting at least since the 1960s, when it was popularized by advertisements for Letraset transfer sheets. Lorem ipsum was introduced to the digital world in the mid-1980s when Aldus employed it in graphic and word-processing templates for its desktop publishing program PageMaker. Other popular word processors including Pages and Microsoft Word have since adopted Lorem ipsum as well."
selection_enabled = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="OpenFile" type="Button" parent="."]
anchor_left = 1.0
@ -66,7 +41,6 @@ offset_left = -158.0
offset_top = -50.0
offset_right = -84.0
offset_bottom = -30.0
scale = Vector2( 2, 2 )
text = "Open File"
[node name="Home" type="Button" parent="."]
@ -78,11 +52,7 @@ offset_left = -478.0
offset_top = -50.0
offset_right = -404.0
offset_bottom = -30.0
scale = Vector2( 2, 2 )
text = "Home"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Copy" type="Button" parent="."]
anchor_top = 1.0
@ -91,11 +61,7 @@ offset_left = 160.0
offset_top = -50.0
offset_right = 234.0
offset_bottom = -30.0
scale = Vector2( 2, 2 )
text = "Copy"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="End" type="Button" parent="."]
anchor_left = 1.0
@ -106,7 +72,6 @@ offset_left = -318.0
offset_top = -50.0
offset_right = -244.0
offset_bottom = -30.0
scale = Vector2( 2, 2 )
text = "End"
[node name="Close" type="Button" parent="."]
@ -116,11 +81,10 @@ offset_left = 10.0
offset_top = -50.0
offset_right = 80.0
offset_bottom = -30.0
scale = Vector2( 2, 2 )
text = "Close"
[connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"]
[connection signal="popup_hide" from="FileDialog" to="." method="_on_FileDialog_popup_hide"]
[connection signal="visibility_changed" from="FileDialog" to="." method="_on_file_dialog_visibility_changed"]
[connection signal="pressed" from="OpenFile" to="." method="_on_OpenFile_pressed"]
[connection signal="pressed" from="Home" to="." method="_on_Home_pressed"]
[connection signal="pressed" from="Copy" to="." method="_on_Copy_pressed"]

View file

@ -31,32 +31,29 @@
var _to_free = []
var _to_queue_free = []
func add_free(thing):
if typeof(thing) == TYPE_OBJECT:
if !thing is RefCounted:
if(typeof(thing) == TYPE_OBJECT):
if(!thing is RefCounted):
_to_free.append(thing)
func add_queue_free(thing):
_to_queue_free.append(thing)
func get_queue_free_count():
return _to_queue_free.size()
func get_free_count():
return _to_free.size()
func free_all():
for i in range(_to_free.size()):
if is_instance_valid(_to_free[i]):
if(is_instance_valid(_to_free[i])):
_to_free[i].free()
_to_free.clear()
for i in range(_to_queue_free.size()):
if is_instance_valid(_to_queue_free[i]):
if(is_instance_valid(_to_queue_free[i])):
_to_queue_free[i].queue_free()
_to_queue_free.clear()

67
addons/gut/awaiter.gd Normal file
View file

@ -0,0 +1,67 @@
extends Node
signal timeout
signal wait_started
var _wait_time = 0.0
var _wait_frames = 0
var _signal_to_wait_on = null
var _elapsed_time = 0.0
var _elapsed_frames = 0
func _physics_process(delta):
if(_wait_time != 0.0):
_elapsed_time += delta
if(_elapsed_time >= _wait_time):
_end_wait()
if(_wait_frames != 0):
_elapsed_frames += 1
if(_elapsed_frames >= _wait_frames):
_end_wait()
func _end_wait():
_wait_time = 0.0
_wait_frames = 0
_signal_to_wait_on = null
_elapsed_time = 0.0
_elapsed_frames = 0
timeout.emit()
const ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_'
func _signal_callback(
arg1=ARG_NOT_SET, arg2=ARG_NOT_SET, arg3=ARG_NOT_SET,
arg4=ARG_NOT_SET, arg5=ARG_NOT_SET, arg6=ARG_NOT_SET,
arg7=ARG_NOT_SET, arg8=ARG_NOT_SET, arg9=ARG_NOT_SET):
_signal_to_wait_on.disconnect(_signal_callback)
# DO NOT _end_wait here. For other parts of the test to get the signal that
# was waited on, we have to wait for a couple more frames. For example, the
# signal_watcher doesn't get the signal in time if we don't do this.
_wait_frames = 2
func wait_for(x):
_wait_time = x
wait_started.emit()
func wait_frames(x):
_wait_frames = x
wait_started.emit()
func wait_for_signal(the_signal, x):
the_signal.connect(_signal_callback)
_signal_to_wait_on = the_signal
_wait_time = x
wait_started.emit()
func is_waiting():
return _wait_time != 0.0 || _wait_frames != 0

View file

@ -1,85 +1,85 @@
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _strutils = _utils.Strutils.new()
var _max_length = 100
var _should_compare_int_to_float = true
const MISSING = "|__missing__gut__compare__value__|"
const DICTIONARY_DISCLAIMER = "Dictionaries are compared-by-ref. See assert_eq in wiki."
func _cannot_comapre_text(v1, v2):
return str(
"Cannot compare ", _strutils.types[typeof(v1)], " with ", _strutils.types[typeof(v2)], "."
)
const MISSING = '|__missing__gut__compare__value__|'
const DICTIONARY_DISCLAIMER = 'Dictionaries are compared-by-ref. See assert_eq in wiki.'
func _cannot_compare_text(v1, v2):
return str('Cannot compare ', _strutils.types[typeof(v1)], ' with ',
_strutils.types[typeof(v2)], '.')
func _make_missing_string(text):
return "<missing " + text + ">"
return '<missing ' + text + '>'
func _create_missing_result(v1, v2, text):
var to_return = null
var v1_str = format_value(v1)
var v2_str = format_value(v2)
if typeof(v1) == TYPE_STRING and v1 == MISSING:
if(typeof(v1) == TYPE_STRING and v1 == MISSING):
v1_str = _make_missing_string(text)
to_return = _utils.CompareResult.new()
elif typeof(v2) == TYPE_STRING and v2 == MISSING:
elif(typeof(v2) == TYPE_STRING and v2 == MISSING):
v2_str = _make_missing_string(text)
to_return = _utils.CompareResult.new()
if to_return != null:
to_return.summary = str(v1_str, " != ", v2_str)
if(to_return != null):
to_return.summary = str(v1_str, ' != ', v2_str)
to_return.are_equal = false
return to_return
func simple(v1, v2, missing_string = ""):
func simple(v1, v2, missing_string=''):
var missing_result = _create_missing_result(v1, v2, missing_string)
if missing_result != null:
if(missing_result != null):
return missing_result
var result = _utils.CompareResult.new()
var cmp_str = null
var extra = ""
var extra = ''
if _should_compare_int_to_float and [2, 3].has(typeof(v1)) and [2, 3].has(typeof(v2)):
result.are_equal = v1 == v2
var tv1 = typeof(v1)
var tv2 = typeof(v2)
elif _utils.are_datatypes_same(v1, v2):
# print(tv1, '::', tv2, ' ', _strutils.types[tv1], '::', _strutils.types[tv2])
if(_should_compare_int_to_float and [TYPE_INT, TYPE_FLOAT].has(tv1) and [TYPE_INT, TYPE_FLOAT].has(tv2)):
result.are_equal = v1 == v2
if typeof(v1) == TYPE_DICTIONARY:
if result.are_equal:
extra = ". Same dictionary ref. "
elif([TYPE_STRING, TYPE_STRING_NAME].has(tv1) and [TYPE_STRING, TYPE_STRING_NAME].has(tv2)):
result.are_equal = v1 == v2
elif(_utils.are_datatypes_same(v1, v2)):
result.are_equal = v1 == v2
if(typeof(v1) == TYPE_DICTIONARY):
if(result.are_equal):
extra = '. Same dictionary ref. '
else:
extra = ". Different dictionary refs. "
extra = '. Different dictionary refs. '
extra += DICTIONARY_DISCLAIMER
if typeof(v1) == TYPE_ARRAY:
if(typeof(v1) == TYPE_ARRAY):
var array_result = _utils.DiffTool.new(v1, v2, _utils.DIFF.SHALLOW)
result.summary = array_result.get_short_summary()
if !array_result.are_equal():
if(!array_result.are_equal):
extra = ".\n" + array_result.get_short_summary()
else:
cmp_str = "!="
cmp_str = '!='
result.are_equal = false
extra = str(". ", _cannot_comapre_text(v1, v2))
extra = str('. ', _cannot_compare_text(v1, v2))
cmp_str = get_compare_symbol(result.are_equal)
if typeof(v1) != TYPE_ARRAY:
result.summary = str(format_value(v1), " ", cmp_str, " ", format_value(v2), extra)
result.summary = str(format_value(v1), ' ', cmp_str, ' ', format_value(v2), extra)
return result
func shallow(v1, v2):
var result = null
var result = null
if _utils.are_datatypes_same(v1, v2):
if typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]:
if(_utils.are_datatypes_same(v1, v2)):
if(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]):
result = _utils.DiffTool.new(v1, v2, _utils.DIFF.SHALLOW)
else:
result = simple(v1, v2)
@ -90,10 +90,10 @@ func shallow(v1, v2):
func deep(v1, v2):
var result = null
var result = null
if _utils.are_datatypes_same(v1, v2):
if typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]:
if(_utils.are_datatypes_same(v1, v2)):
if(typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]):
result = _utils.DiffTool.new(v1, v2, _utils.DIFF.DEEP)
else:
result = simple(v1, v2)
@ -103,17 +103,17 @@ func deep(v1, v2):
return result
func format_value(val, max_val_length = _max_length):
func format_value(val, max_val_length=_max_length):
return _strutils.truncate_string(_strutils.type2str(val), max_val_length)
func compare(v1, v2, diff_type = _utils.DIFF.SIMPLE):
func compare(v1, v2, diff_type=_utils.DIFF.SIMPLE):
var result = null
if diff_type == _utils.DIFF.SIMPLE:
if(diff_type == _utils.DIFF.SIMPLE):
result = simple(v1, v2)
elif diff_type == _utils.DIFF.SHALLOW:
elif(diff_type == _utils.DIFF.SHALLOW):
result = shallow(v1, v2)
elif diff_type == _utils.DIFF.DEEP:
elif(diff_type == _utils.DIFF.DEEP):
result = deep(v1, v2)
return result
@ -128,7 +128,7 @@ func set_should_compare_int_to_float(should_compare_int_float):
func get_compare_symbol(is_equal):
if is_equal:
return "=="
if(is_equal):
return '=='
else:
return "!="
return '!='

View file

@ -1,76 +1,70 @@
var are_equal = null :
var _are_equal = false
var are_equal = false :
get:
return are_equal # TODOConverter40 Copy here content of get_are_equal
set(mod_value):
mod_value # TODOConverter40 Copy here content of set_are_equal
return get_are_equal()
set(val):
set_are_equal(val)
var _summary = null
var summary = null :
get:
return summary # TODOConverter40 Copy here content of get_summary
set(mod_value):
mod_value # TODOConverter40 Copy here content of set_summary
return get_summary()
set(val):
set_summary(val)
var _max_differences = 30
var max_differences = 30 :
get:
return max_differences # TODOConverter40 Copy here content of get_max_differences
set(mod_value):
mod_value # TODOConverter40 Copy here content of set_max_differences
var differences = {} :
get:
return differences # TODOConverter40 Copy here content of get_differences
set(mod_value):
mod_value # TODOConverter40 Copy here content of set_differences
return get_max_differences()
set(val):
set_max_differences(val)
var _differences = {}
var differences :
get:
return get_differences()
set(val):
set_differences(val)
func _block_set(which, val):
push_error(str("cannot set ", which, ", value [", val, "] ignored."))
push_error(str('cannot set ', which, ', value [', val, '] ignored.'))
func _to_string():
return str(get_summary()) # could be null, gotta str it.
return str(get_summary()) # could be null, gotta str it.
func get_are_equal():
return are_equal
return _are_equal
func set_are_equal(r_eq):
are_equal = r_eq
_are_equal = r_eq
func get_summary():
return summary
return _summary
func set_summary(smry):
summary = smry
_summary = smry
func get_total_count():
pass
func get_different_count():
pass
func get_short_summary():
return summary
func get_max_differences():
return max_differences
return _max_differences
func set_max_differences(max_diff):
max_differences = max_diff
_max_differences = max_diff
func get_differences():
return differences
return _differences
func set_differences(diffs):
_block_set("differences", diffs)
_block_set('differences', diffs)
func get_brackets():
return null

View file

@ -1,24 +1,20 @@
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _strutils = _utils.Strutils.new()
const INDENT = " "
const INDENT = ' '
var _max_to_display = 30
const ABSOLUTE_MAX_DISPLAYED = 10000
const UNLIMITED = -1
func _single_diff(diff, depth = 0):
func _single_diff(diff, depth=0):
var to_return = ""
var brackets = diff.get_brackets()
if brackets != null and !diff.are_equal:
to_return = ""
to_return += str(
brackets.open,
"\n",
_strutils.indent_text(differences_to_s(diff.differences, depth), depth + 1, INDENT),
"\n",
brackets.close
)
if(brackets != null and !diff.are_equal):
to_return = ''
to_return += str(brackets.open, "\n",
_strutils.indent_text(differences_to_s(diff.differences, depth), depth+1, INDENT), "\n",
brackets.close)
else:
to_return = str(diff)
@ -26,20 +22,20 @@ func _single_diff(diff, depth = 0):
func make_it(diff):
var to_return = ""
if diff.are_equal:
var to_return = ''
if(diff.are_equal):
to_return = diff.summary
else:
if _max_to_display == ABSOLUTE_MAX_DISPLAYED:
to_return = str(diff.get_value_1(), " != ", diff.get_value_2())
if(_max_to_display == ABSOLUTE_MAX_DISPLAYED):
to_return = str(diff.get_value_1(), ' != ', diff.get_value_2())
else:
to_return = diff.get_short_summary()
to_return += str("\n", _strutils.indent_text(_single_diff(diff, 0), 1, " "))
to_return += str("\n", _strutils.indent_text(_single_diff(diff, 0), 1, ' '))
return to_return
func differences_to_s(differences, depth = 0):
var to_return = ""
func differences_to_s(differences, depth=0):
var to_return = ''
var keys = differences.keys()
keys.sort()
var limit = min(_max_to_display, differences.size())
@ -48,10 +44,10 @@ func differences_to_s(differences, depth = 0):
var key = keys[i]
to_return += str(key, ": ", _single_diff(differences[key], depth))
if i != limit - 1:
if(i != limit -1):
to_return += "\n"
if differences.size() > _max_to_display:
if(differences.size() > _max_to_display):
to_return += str("\n\n... ", differences.size() - _max_to_display, " more.")
return to_return
@ -63,5 +59,6 @@ func get_max_to_display():
func set_max_to_display(max_to_display):
_max_to_display = max_to_display
if _max_to_display == UNLIMITED:
if(_max_to_display == UNLIMITED):
_max_to_display = ABSOLUTE_MAX_DISPLAYED

View file

@ -1,11 +1,15 @@
extends "res://addons/gut/compare_result.gd"
const INDENT = " "
enum { DEEP, SHALLOW, SIMPLE }
extends 'res://addons/gut/compare_result.gd'
const INDENT = ' '
enum {
DEEP,
SHALLOW,
SIMPLE
}
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _strutils = _utils.Strutils.new()
var _compare = _utils.Comparator.new()
var DiffTool = load("res://addons/gut/diff_tool.gd")
var DiffTool = load('res://addons/gut/diff_tool.gd')
var _value_1 = null
var _value_2 = null
@ -13,59 +17,42 @@ var _total_count = 0
var _diff_type = null
var _brackets = null
var _valid = true
var _desc_things = "somethings"
var _desc_things = 'somethings'
# -------- comapre_result.gd "interface" ---------------------
func set_are_equal(val):
_block_set("are_equal", val)
_block_set('are_equal', val)
func get_are_equal():
return are_equal()
if(!_valid):
return null
else:
return differences.size() == 0
func set_summary(val):
_block_set("summary", val)
_block_set('summary', val)
func get_summary():
return summarize()
func get_different_count():
return differences.size()
func get_total_count():
func get_total_count():
return _total_count
func get_short_summary():
var text = str(
_strutils.truncate_string(str(_value_1), 50),
" ",
_compare.get_compare_symbol(are_equal()),
" ",
_strutils.truncate_string(str(_value_2), 50)
)
if !are_equal():
text += str(
" ",
get_different_count(),
" of ",
get_total_count(),
" ",
_desc_things,
" do not match."
)
var text = str(_strutils.truncate_string(str(_value_1), 50),
' ', _compare.get_compare_symbol(are_equal), ' ',
_strutils.truncate_string(str(_value_2), 50))
if(!are_equal):
text += str(' ', get_different_count(), ' of ', get_total_count(),
' ', _desc_things, ' do not match.')
return text
func get_brackets():
return _brackets
# -------- comapre_result.gd "interface" ---------------------
@ -74,7 +61,7 @@ func _invalidate():
differences = null
func _init(v1, v2 ,diff_type = DEEP):
func _init(v1,v2,diff_type=DEEP):
_value_1 = v1
_value_2 = v2
_diff_type = diff_type
@ -83,41 +70,41 @@ func _init(v1, v2 ,diff_type = DEEP):
func _find_differences(v1, v2):
if _utils.are_datatypes_same(v1, v2):
if typeof(v1) == TYPE_ARRAY:
_brackets = {"open": "[", "close": "]"}
_desc_things = "indexes"
if(_utils.are_datatypes_same(v1, v2)):
if(typeof(v1) == TYPE_ARRAY):
_brackets = {'open':'[', 'close':']'}
_desc_things = 'indexes'
_diff_array(v1, v2)
elif typeof(v2) == TYPE_DICTIONARY:
_brackets = {"open": "{", "close": "}"}
_desc_things = "keys"
elif(typeof(v2) == TYPE_DICTIONARY):
_brackets = {'open':'{', 'close':'}'}
_desc_things = 'keys'
_diff_dictionary(v1, v2)
else:
_invalidate()
_utils.get_logger().error("Only Arrays and Dictionaries are supported.")
_utils.get_logger().error('Only Arrays and Dictionaries are supported.')
else:
_invalidate()
_utils.get_logger().error("Only Arrays and Dictionaries are supported.")
_utils.get_logger().error('Only Arrays and Dictionaries are supported.')
func _diff_array(a1, a2):
_total_count = max(a1.size(), a2.size())
for i in range(a1.size()):
var result = null
if i < a2.size():
if _diff_type == DEEP:
if(i < a2.size()):
if(_diff_type == DEEP):
result = _compare.deep(a1[i], a2[i])
else:
result = _compare.simple(a1[i], a2[i])
else:
result = _compare.simple(a1[i], _compare.MISSING, "index")
result = _compare.simple(a1[i], _compare.MISSING, 'index')
if !result.are_equal:
if(!result.are_equal):
differences[i] = result
if a1.size() < a2.size():
if(a1.size() < a2.size()):
for i in range(a1.size(), a2.size()):
differences[i] = _compare.simple(_compare.MISSING, a2[i], "index")
differences[i] = _compare.simple(_compare.MISSING, a2[i], 'index')
func _diff_dictionary(d1, d2):
@ -127,46 +114,39 @@ func _diff_dictionary(d1, d2):
# Process all the keys in d1
_total_count += d1_keys.size()
for key in d1_keys:
if !d2.has(key):
differences[key] = _compare.simple(d1[key], _compare.MISSING, "key")
if(!d2.has(key)):
differences[key] = _compare.simple(d1[key], _compare.MISSING, 'key')
else:
d2_keys.remove_at(d2_keys.find(key))
var result = null
if _diff_type == DEEP:
if(_diff_type == DEEP):
result = _compare.deep(d1[key], d2[key])
else:
result = _compare.simple(d1[key], d2[key])
if !result.are_equal:
if(!result.are_equal):
differences[key] = result
# Process all the keys in d2 that didn't exist in d1
_total_count += d2_keys.size()
for i in range(d2_keys.size()):
differences[d2_keys[i]] = _compare.simple(_compare.MISSING, d2[d2_keys[i]], "key")
differences[d2_keys[i]] = _compare.simple(_compare.MISSING, d2[d2_keys[i]], 'key')
func summarize():
var summary = ""
var summary = ''
if are_equal():
if(are_equal):
summary = get_short_summary()
else:
var formatter = load("res://addons/gut/diff_formatter.gd").new()
var formatter = load('res://addons/gut/diff_formatter.gd').new()
formatter.set_max_to_display(max_differences)
summary = formatter.make_it(self)
return summary
func are_equal():
if !_valid:
return null
else:
return differences.size() == 0
func get_diff_type():
return _diff_type

View file

@ -1,6 +1,6 @@
{func_decleration}
__gut_spy('{method_name}', {param_array})
if(__gut_should_call_super('{method_name}', {param_array})):
__gutdbl.spy_on('{method_name}', {param_array})
if(__gutdbl.should_call_super('{method_name}', {param_array})):
return {super_call}
else:
return __gut_get_stubbed_return('{method_name}', {param_array})
return __gutdbl.get_stubbed_return('{method_name}', {param_array})

View file

@ -0,0 +1,4 @@
{func_decleration}:
super({super_params})
__gutdbl.spy_on('{method_name}', {param_array})

View file

@ -1,59 +1,31 @@
# ##############################################################################
# Start Script
# Gut Doubled Script
# ##############################################################################
{extends}
{constants}
{properties}
# ------------------------------------------------------------------------------
# GUT Double properties and methods
# GUT stuff
# ------------------------------------------------------------------------------
var __gut_metadata_ = {
path = '{path}',
var __gutdbl_values = {
double = self,
thepath = '{path}',
subpath = '{subpath}',
stubber = __gut_instance_from_id({stubber_id}),
spy = __gut_instance_from_id({spy_id}),
gut = __gut_instance_from_id({gut_id}),
stubber = {stubber_id},
spy = {spy_id},
gut = {gut_id},
from_singleton = '{singleton_name}',
is_partial = {is_partial}
is_partial = {is_partial},
}
var __gutdbl = load('res://addons/gut/double_tools.gd').new(__gutdbl_values)
func __gut_instance_from_id(inst_id):
if(inst_id == -1):
return null
else:
return instance_from_id(inst_id)
func __gut_should_call_super(method_name, called_with):
if(__gut_metadata_.stubber != null):
return __gut_metadata_.stubber.should_call_super(self, method_name, called_with)
else:
return false
var __gut_utils_ = load('res://addons/gut/utils.gd').get_instance()
func __gut_spy(method_name, called_with):
if(__gut_metadata_.spy != null):
__gut_metadata_.spy.add_call(self, method_name, called_with)
func __gut_get_stubbed_return(method_name, called_with):
if(__gut_metadata_.stubber != null):
return __gut_metadata_.stubber.get_return(self, method_name, called_with)
else:
return null
func __gut_default_val(method_name, p_index):
if(__gut_metadata_.stubber != null):
return __gut_metadata_.stubber.get_default_value(self, method_name, p_index)
else:
return null
func _init():
if(__gut_metadata_.gut != null):
__gut_metadata_.gut.get_autofree().add_free(self)
# Here so other things can check for a method to know if this is a double.
func __gutdbl_check_method__():
pass
# ------------------------------------------------------------------------------
# Methods start here
# Doubled Methods
# ------------------------------------------------------------------------------

View file

@ -0,0 +1,52 @@
var thepath = ''
var subpath = ''
var stubber = null
var spy = null
var gut = null
var from_singleton = null
var is_partial = null
var double = null
const NO_DEFAULT_VALUE = '!__gut__no__default__value__!'
func from_id(inst_id):
if(inst_id == -1):
return null
else:
return instance_from_id(inst_id)
func should_call_super(method_name, called_with):
if(stubber != null):
return stubber.should_call_super(double, method_name, called_with)
else:
return false
func spy_on(method_name, called_with):
if(spy != null):
spy.add_call(double, method_name, called_with)
func get_stubbed_return(method_name, called_with):
if(stubber != null):
return stubber.get_return(double, method_name, called_with)
else:
return null
func default_val(method_name, p_index, default_val=NO_DEFAULT_VALUE):
if(stubber != null):
return stubber.get_default_value(double, method_name, p_index)
else:
return null
func _init(values=null):
if(values != null):
double = values.double
thepath = values.thepath
subpath = values.subpath
stubber = from_id(values.stubber)
spy = from_id(values.spy)
gut = from_id(values.gut)
from_singleton = values.from_singleton
is_partial = values.is_partial
if(gut != null):
gut.get_autofree().add_free(double)

View file

@ -30,304 +30,24 @@
# -----------
# ##############################################################################
# ------------------------------------------------------------------------------
# Utility class to hold the local and built in methods separately. Add all local
# methods FIRST, then add built ins.
# ------------------------------------------------------------------------------
class ScriptMethods:
# List of methods that should not be overloaded when they are not defined
# in the class being doubled. These either break things if they are
# overloaded or do not have a "super" equivalent so we can't just pass
# through.
var _blacklist = [
"has_method",
"get_script",
"get",
"_notification",
"get_path",
"_enter_tree",
"_exit_tree",
"_process",
"_draw",
"_physics_process",
"_input",
"_unhandled_input",
"_unhandled_key_input",
"_set",
"_get", # probably
"emit_signal", # can't handle extra parameters to be sent with signal.
"draw_mesh", # issue with one parameter, value is `Null((..), (..), (..))``
"_to_string", # nonexistant function ._to_string
"_get_minimum_size", # Nonexistent function _get_minimum_size
]
# These methods should not be included in the double.
var _skip = [
# There is an init in the template. There is also no real reason
# to include this method since it will always be called, it has no
# return value, and you cannot prevent super from being called.
"_init"
]
var built_ins = []
var local_methods = []
var _method_names = []
func is_blacklisted(method_meta):
return _blacklist.find(method_meta.name) != -1
func _add_name_if_does_not_have(method_name):
if _skip.has(method_name):
return false
var should_add = _method_names.find(method_name) == -1
if should_add:
_method_names.append(method_name)
return should_add
func add_built_in_method(method_meta):
var did_add = _add_name_if_does_not_have(method_meta.name)
if did_add and !is_blacklisted(method_meta):
built_ins.append(method_meta)
func add_local_method(method_meta):
var did_add = _add_name_if_does_not_have(method_meta.name)
if did_add:
local_methods.append(method_meta)
func to_s():
var text = "Locals\n"
for i in range(local_methods.size()):
text += str(" ", local_methods[i].name, "\n")
text += "Built-Ins\n"
for i in range(built_ins.size()):
text += str(" ", built_ins[i].name, "\n")
return text
# ------------------------------------------------------------------------------
# Helper class to deal with objects and inner classes.
# ------------------------------------------------------------------------------
class ObjectInfo:
var _path = null
var _subpaths = []
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _lgr = _utils.get_logger()
var _method_strategy = null
var make_partial_double = false
var scene_path = null
var _native_class = null
var _native_class_name = null
var _singleton_instance = null
var _singleton_name = null
func _init(path, subpath = null):
_path = path
if subpath != null:
_subpaths = Array(subpath.split("/"))
# Returns an instance of the class/inner class
func instantiate():
var to_return = null
if _singleton_instance != null:
to_return = _singleton_instance
elif is_native():
to_return = _native_class.new()
else:
to_return = get_loaded_class().new()
return to_return
# Can't call it get_class because that is reserved so it gets this ugly name.
# Loads up the class and then any inner classes to give back a reference to
# the desired Inner class (if there is any)
func get_loaded_class():
var LoadedClass = load(_path)
for i in range(_subpaths.size()):
LoadedClass = LoadedClass.get(_subpaths[i])
return LoadedClass
func to_s():
return str(_path, "[", get_subpath(), "]")
func get_path():
return _path
func get_subpath():
return "/".join(PackedStringArray(_subpaths))
func has_subpath():
return _subpaths.size() != 0
func get_method_strategy():
return _method_strategy
func set_method_strategy(method_strategy):
_method_strategy = method_strategy
func is_native():
return _native_class != null
func set_native_class(native_class):
_native_class = native_class
var inst = native_class.new()
_native_class_name = inst.get_class()
_path = _native_class_name
if !inst is RefCounted:
inst.free()
func get_native_class_name():
return _native_class_name
func get_singleton_instance():
return _singleton_instance
func get_singleton_name():
return _singleton_name
func set_singleton_name(singleton_name):
_singleton_name = singleton_name
_singleton_instance = _utils.get_singleton_by_name(_singleton_name)
func is_singleton():
return _singleton_instance != null
func get_extends_text():
var extend = null
if is_singleton():
extend = str("# Double of singleton ", _singleton_name, ", base class is RefCounted")
elif is_native():
var native = get_native_class_name()
if native.begins_with("_"):
native = native.substr(1)
extend = str("extends ", native)
else:
extend = str("extends '", get_path(), "'")
if has_subpath():
extend += str(".", get_subpath().replace("/", "."))
return extend
func get_constants_text():
if !is_singleton():
return ""
# do not include constants defined in the super class which for
# singletons stubs is RefCounted.
var exclude_constants = Array(ClassDB.class_get_integer_constant_list("RefCounted"))
var text = str("# -----\n# ", _singleton_name, " Constants\n# -----\n")
var constants = ClassDB.class_get_integer_constant_list(_singleton_name)
for c in constants:
if !exclude_constants.has(c):
var value = ClassDB.class_get_integer_constant(_singleton_name, c)
text += str("const ", c, " = ", value, "\n")
return text
func get_properties_text():
if !is_singleton():
return ""
var text = str("# -----\n# ", _singleton_name, " Properties\n# -----\n")
var props = ClassDB.class_get_property_list(_singleton_name)
for prop in props:
var accessors = {"setter": null, "getter": null}
var prop_text = str("var ", prop["name"])
var getter_name = "get_" + prop["name"]
if ClassDB.class_has_method(_singleton_name, getter_name):
accessors.getter = getter_name
else:
getter_name = "is_" + prop["name"]
if ClassDB.class_has_method(_singleton_name, getter_name):
accessors.getter = getter_name
var setter_name = "set_" + prop["name"]
if ClassDB.class_has_method(_singleton_name, setter_name):
accessors.setter = setter_name
var setget_text = ""
if accessors.setter != null and accessors.getter != null:
setget_text = str("setget ", accessors.setter, ", ", accessors.getter)
else:
# never seen this message show up, but it should show up if we
# get misbehaving singleton.
_lgr.error(
str(
"Could not find setget methods for property: ",
_singleton_name,
".",
prop["name"]
)
)
text += str(prop_text, " ", setget_text, "\n")
return text
# ------------------------------------------------------------------------------
# Allows for interacting with a file but only creating a string. This was done
# to ease the transition from files being created for doubles to loading
# doubles from a string. This allows the files to be created for debugging
# purposes since reading a file is easier than reading a dumped out string.
# ------------------------------------------------------------------------------
class FileOrString:
extends File
var _do_file = false
var _contents = ""
var _path = null
func open(path, mode):
_path = path
if _do_file:
return super.open(path, mode)
else:
return OK
func close():
if _do_file:
return super.close()
func store_string(s):
if _do_file:
super.store_string(s)
_contents += s
func get_contents():
return _contents
func get_path():
return _path
func load_it():
if _contents != "":
var script = GDScript.new()
script.set_source_code(get_contents())
script.reload()
return script
else:
return load(_path)
# ------------------------------------------------------------------------------
# A stroke of genius if I do say so. This allows for doubling a scene without
# having to write any files. By overloading the "instance" method we can
# having to write any files. By overloading the "instantiate" method we can
# make whatever we want.
# ------------------------------------------------------------------------------
class PackedSceneDouble:
extends PackedScene
var _script = null
var _script = null
var _scene = null
func set_script_obj(obj):
_script = obj
func instance(edit_state = 0):
func instantiate(edit_state=0):
var inst = _scene.instantiate(edit_state)
if _script != null:
if(_script != null):
inst.set_script(_script)
return inst
@ -335,429 +55,259 @@ class PackedSceneDouble:
_scene = load(path)
# ------------------------------------------------------------------------------
# START Doubler
# ------------------------------------------------------------------------------
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _ignored_methods = _utils.OneToMany.new()
var _stubber = _utils.Stubber.new()
var _lgr = _utils.get_logger()
var _method_maker = _utils.MethodMaker.new()
var _output_dir = "user://gut_temp_directory"
var _double_count = 0 # used in making files names unique
var _spy = null
var _gut = null
var _strategy = null
var _base_script_text = _utils.get_file_as_text(
"res://addons/gut/double_templates/script_template.txt"
)
var _make_files = false
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _base_script_text = _utils.get_file_as_text('res://addons/gut/double_templates/script_template.txt')
var _script_collector = _utils.ScriptCollector.new()
# used by tests for debugging purposes.
var _print_source = false
var print_source = false
var inner_class_registry = _utils.InnerClassRegistry.new()
# ###############
# Properties
# ###############
var _stubber = _utils.Stubber.new()
func get_stubber():
return _stubber
func set_stubber(stubber):
_stubber = stubber
func _init(strategy = _utils.DOUBLE_STRATEGY.PARTIAL):
set_logger(_utils.get_logger())
var _lgr = _utils.get_logger()
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger
_method_maker.set_logger(logger)
var _spy = null
func get_spy():
return _spy
func set_spy(spy):
_spy = spy
var _gut = null
func get_gut():
return _gut
func set_gut(gut):
_gut = gut
var _strategy = null
func get_strategy():
return _strategy
func set_strategy(strategy):
_strategy = strategy
var _method_maker = _utils.MethodMaker.new()
func get_method_maker():
return _method_maker
var _ignored_methods = _utils.OneToMany.new()
func get_ignored_methods():
return _ignored_methods
# ###############
# Private
# ###############
func _init(strategy=_utils.DOUBLE_STRATEGY.SCRIPT_ONLY):
set_logger(_utils.get_logger())
_strategy = strategy
func _get_indented_line(indents, text):
var to_return = ""
var to_return = ''
for _i in range(indents):
to_return += "\t"
return str(to_return, text, "\n")
func _stub_to_call_super(obj_info, method_name):
if _utils.non_super_methods.has(method_name):
func _stub_to_call_super(parsed, method_name):
if(_utils.non_super_methods.has(method_name)):
return
var path = obj_info.get_path()
if obj_info.is_singleton():
path = obj_info.get_singleton_name()
elif obj_info.scene_path != null:
path = obj_info.scene_path
var params = _utils.StubParams.new(path, method_name, obj_info.get_subpath())
var params = _utils.StubParams.new(parsed.script_path, method_name, parsed.subpath)
params.to_call_super()
_stubber.add_stub(params)
func _get_base_script_text(obj_info, override_path):
var path = obj_info.get_path()
if override_path != null:
func _get_base_script_text(parsed, override_path, partial):
var path = parsed.script_path
if(override_path != null):
path = override_path
var stubber_id = -1
if _stubber != null:
if(_stubber != null):
stubber_id = _stubber.get_instance_id()
var spy_id = -1
if _spy != null:
if(_spy != null):
spy_id = _spy.get_instance_id()
var gut_id = -1
if _gut != null:
if(_gut != null):
gut_id = _gut.get_instance_id()
var extends_text = parsed.get_extends_text()
var values = {
# Top sections
"extends": obj_info.get_extends_text(),
"constants": obj_info.get_constants_text(),
"properties": obj_info.get_properties_text(),
"extends":extends_text,
"constants":'',#obj_info.get_constants_text(),
"properties":'',#obj_info.get_properties_text(),
# metadata values
"path": path,
"subpath": obj_info.get_subpath(),
"stubber_id": stubber_id,
"spy_id": spy_id,
"gut_id": gut_id,
"singleton_name": _utils.nvl(obj_info.get_singleton_name(), ""),
"is_partial": str(obj_info.make_partial_double).to_lower()
"path":path,
"subpath":_utils.nvl(parsed.subpath, ''),
"stubber_id":stubber_id,
"spy_id":spy_id,
"gut_id":gut_id,
"singleton_name":'',#_utils.nvl(obj_info.get_singleton_name(), ''),
"is_partial":partial,
}
return _base_script_text.format(values)
func _write_file(obj_info, dest_path, override_path = null):
var base_script = _get_base_script_text(obj_info, override_path)
var script_methods = _get_methods(obj_info)
func _create_double(parsed, strategy, override_path, partial):
var base_script = _get_base_script_text(parsed, override_path, partial)
var super_name = ""
var path = ""
if obj_info.is_singleton():
super_name = obj_info.get_singleton_name()
else:
path = obj_info.get_path()
path = parsed.script_path
var dbl_src = ""
dbl_src += base_script
var f = FileOrString.new()
f._do_file = _make_files
var f_result = f.open(dest_path, f.WRITE)
for method in parsed.get_local_methods():
if(!method.is_black_listed() && !_ignored_methods.has(parsed.resource, method.meta.name)):
var mthd = parsed.get_local_method(method.meta.name)
dbl_src += _get_func_text(method.meta, path, super_name)
if f_result != OK:
_lgr.error(str("Error creating file ", dest_path))
_lgr.error(str("Could not create double for :", obj_info.to_s()))
return
if(strategy == _utils.DOUBLE_STRATEGY.INCLUDE_SUPER):
for method in parsed.get_super_methods():
if(!method.is_black_listed() && !_ignored_methods.has(parsed.resource, method.meta.name)):
_stub_to_call_super(parsed, method.meta.name)
dbl_src += _get_func_text(method.meta, path, super_name)
f.store_string(base_script)
if(print_source):
print(_utils.add_line_numbers(dbl_src))
for i in range(script_methods.local_methods.size()):
f.store_string(_get_func_text(script_methods.local_methods[i], path, super_name))
var DblClass = _utils.create_script_from_source(dbl_src)
if(_stubber != null):
_stub_method_default_values(DblClass, parsed, strategy)
for i in range(script_methods.built_ins.size()):
_stub_to_call_super(obj_info, script_methods.built_ins[i].name)
f.store_string(_get_func_text(script_methods.built_ins[i], path, super_name))
f.close()
if _print_source:
print(f.get_contents())
return f
return DblClass
func _double_scene_and_script(scene_info):
func _stub_method_default_values(which, parsed, strategy):
for method in parsed.get_local_methods():
if(!method.is_black_listed() && !_ignored_methods.has(parsed.resource, method.meta.name)):
_stubber.stub_defaults_from_meta(parsed.script_path, method.meta)
func _double_scene_and_script(scene, strategy, partial):
var to_return = PackedSceneDouble.new()
to_return.load_scene(scene_info.get_path())
to_return.load_scene(scene.get_path())
var inst = load(scene_info.get_path()).instantiate()
var script_path = null
if inst.get_script():
script_path = inst.get_script().get_path()
inst.free()
if script_path:
var oi = ObjectInfo.new(script_path)
oi.set_method_strategy(scene_info.get_method_strategy())
oi.make_partial_double = scene_info.make_partial_double
oi.scene_path = scene_info.get_path()
to_return.set_script_obj(_double(oi, scene_info.get_path()).load_it())
var script_obj = _utils.get_scene_script_object(scene)
if(script_obj != null):
var script_dbl = null
if(partial):
script_dbl = _partial_double(script_obj, strategy, scene.get_path())
else:
script_dbl = _double(script_obj, strategy, scene.get_path())
to_return.set_script_obj(script_dbl)
return to_return
func _get_methods(object_info):
var obj = object_info.instantiate()
# any method in the script or super script
var script_methods = ScriptMethods.new()
var methods = obj.get_method_list()
if !object_info.is_singleton() and !(obj is RefCounted):
obj.free()
# first pass is for local methods only
for i in range(methods.size()):
if object_info.is_singleton():
#print(methods[i].name, " :: ", methods[i].flags, " :: ", methods[i].id)
#print(" ", methods[i])
# It appears that the ID for methods upstream from a singleton are
# below 200. Initially it was thought that singleton specific methods
# were above 1000. This was true for Input but not for OS. I've
# changed the condition to be > 200 instead of > 1000. It will take
# some investigation to figure out if this is right, but it works
# for now. Someone either find an issue and open a bug, or this will
# just exist like this. Sorry future me (or someone else).
if methods[i].id > 200 and methods[i].flags in [1, 9]:
script_methods.add_local_method(methods[i])
# 65 is a magic number for methods in script, though documentation
# says 64. This picks up local overloads of base class methods too.
# See MethodFlags in @GlobalScope
elif (
methods[i].flags == 65
and !_ignored_methods.has(object_info.get_path(), methods[i]["name"])
):
script_methods.add_local_method(methods[i])
if object_info.get_method_strategy() == _utils.DOUBLE_STRATEGY.FULL:
# second pass is for anything not local
for j in range(methods.size()):
# 65 is a magic number for methods in script, though documentation
# says 64. This picks up local overloads of base class methods too.
if (
methods[j].flags != 65
and !_ignored_methods.has(object_info.get_path(), methods[j]["name"])
):
script_methods.add_built_in_method(methods[j])
return script_methods
func _get_inst_id_ref_str(inst):
var ref_str = "null"
if inst:
ref_str = str("instance_from_id(", inst.get_instance_id(), ")")
var ref_str = 'null'
if(inst):
ref_str = str('instance_from_id(', inst.get_instance_id(),')')
return ref_str
func _get_func_text(method_hash, path, super = ""):
var override_count = null
if _stubber != null:
func _get_func_text(method_hash, path, super_=""):
var override_count = null;
if(_stubber != null):
override_count = _stubber.get_parameter_count(path, method_hash.name)
if(override_count != null):
print(method_hash.name, ' override: ', override_count)
var text = _method_maker.get_function_text(method_hash, path, override_count, super) + "\n"
var text = _method_maker.get_function_text(method_hash, path, override_count, super_) + "\n"
return text
# returns the path to write the double file to
func _get_temp_path(object_info):
var file_name = null
var extension = null
func _parse_script(obj):
var parsed = null
if object_info.is_singleton():
file_name = str(object_info.get_singleton_instance())
extension = "gd"
elif object_info.is_native():
file_name = object_info.get_native_class_name()
extension = "gd"
if(_utils.is_inner_class(obj)):
if(inner_class_registry.has(obj)):
parsed = _script_collector.parse(inner_class_registry.get_base_resource(obj), obj)
else:
_lgr.error('Doubling Inner Classes requires you register them first. Call register_inner_classes passing the script that contains the inner class.')
else:
file_name = object_info.get_path().get_file().get_basename()
extension = object_info.get_path().get_extension()
parsed = _script_collector.parse(obj)
if object_info.has_subpath():
file_name += "__" + object_info.get_subpath().replace("/", "__")
file_name += str("__dbl", _double_count, "__.", extension)
var to_return = _output_dir.plus_file(file_name)
return to_return
return parsed
func _double(obj_info, override_path = null):
var temp_path = _get_temp_path(obj_info)
var result = _write_file(obj_info, temp_path, override_path)
_double_count += 1
return result
# Override path is used with scenes.
func _double(obj, strategy, override_path=null):
var parsed = _parse_script(obj)
if(parsed != null):
return _create_double(parsed, strategy, override_path, false)
func _double_script(path, make_partial, strategy):
var oi = ObjectInfo.new(path)
oi.make_partial_double = make_partial
oi.set_method_strategy(strategy)
return _double(oi).load_it()
func _partial_double(obj, strategy, override_path=null):
var parsed = _parse_script(obj)
if(parsed != null):
return _create_double(parsed, strategy, override_path, true)
func _double_inner(path, subpath, make_partial, strategy):
var oi = ObjectInfo.new(path, subpath)
oi.set_method_strategy(strategy)
oi.make_partial_double = make_partial
return _double(oi).load_it()
func _double_scene(path, make_partial, strategy):
var oi = ObjectInfo.new(path)
oi.set_method_strategy(strategy)
oi.make_partial_double = make_partial
return _double_scene_and_script(oi)
func _double_gdnative(native_class, make_partial, strategy):
var oi = ObjectInfo.new(null)
oi.set_native_class(native_class)
oi.set_method_strategy(strategy)
oi.make_partial_double = make_partial
return _double(oi).load_it()
func _double_singleton(singleton_name, make_partial, strategy):
var oi = ObjectInfo.new(null)
oi.set_singleton_name(singleton_name)
oi.set_method_strategy(_utils.DOUBLE_STRATEGY.PARTIAL)
oi.make_partial_double = make_partial
return _double(oi).load_it()
# ###############
# -------------------------
# Public
# ###############
func get_output_dir():
return _output_dir
# -------------------------
# double a script/object
func double(obj, strategy=_strategy):
return _double(obj, strategy)
func set_output_dir(output_dir):
if output_dir != null:
_output_dir = output_dir
if _make_files:
var d = Directory.new()
d.make_dir_recursive(output_dir)
func get_spy():
return _spy
func set_spy(spy):
_spy = spy
func get_stubber():
return _stubber
func set_stubber(stubber):
_stubber = stubber
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger
_method_maker.set_logger(logger)
func get_strategy():
return _strategy
func set_strategy(strategy):
_strategy = strategy
func get_gut():
return _gut
func set_gut(gut):
_gut = gut
func partial_double_scene(path, strategy = _strategy):
return _double_scene(path, true, strategy)
func partial_double(obj, strategy=_strategy):
return _partial_double(obj, strategy)
# double a scene
func double_scene(path, strategy = _strategy):
return _double_scene(path, false, strategy)
func double_scene(scene, strategy=_strategy):
return _double_scene_and_script(scene, strategy, false)
func partial_double_scene(scene, strategy=_strategy):
return _double_scene_and_script(scene, strategy, true)
# double a script/object
func double(path, strategy = _strategy):
return _double_script(path, false, strategy)
func double_gdnative(which):
return _double(which, _utils.DOUBLE_STRATEGY.INCLUDE_SUPER)
func partial_double_gdnative(which):
return _partial_double(which, _utils.DOUBLE_STRATEGY.INCLUDE_SUPER)
func partial_double(path, strategy = _strategy):
return _double_script(path, true, strategy)
func double_inner(parent, inner, strategy=_strategy):
var parsed = _script_collector.parse(parent, inner)
return _create_double(parsed, strategy, null, false)
func partial_double_inner(path, subpath, strategy = _strategy):
return _double_inner(path, subpath, true, strategy)
func partial_double_inner(parent, inner, strategy=_strategy):
var parsed = _script_collector.parse(parent, inner)
return _create_double(parsed, strategy, null, true)
# double an inner class in a script
func double_inner(path, subpath, strategy = _strategy):
return _double_inner(path, subpath, false, strategy)
# must always use FULL strategy since this is a native class and you won't get
# any methods if you don't use FULL
func double_gdnative(native_class):
return _double_gdnative(native_class, false, _utils.DOUBLE_STRATEGY.FULL)
# must always use FULL strategy since this is a native class and you won't get
# any methods if you don't use FULL
func partial_double_gdnative(native_class):
return _double_gdnative(native_class, true, _utils.DOUBLE_STRATEGY.FULL)
func double_singleton(name):
return _double_singleton(name, false, _utils.DOUBLE_STRATEGY.PARTIAL)
func partial_double_singleton(name):
return _double_singleton(name, true, _utils.DOUBLE_STRATEGY.PARTIAL)
func clear_output_directory():
if !_make_files:
return false
var did = false
if _output_dir.find("user://") == 0:
var d = Directory.new()
var result = d.open(_output_dir)
# BIG GOTCHA HERE. If it cannot open the dir w/ erro 31, then the
# directory becomes res:// and things go on normally and gut clears out
# out res:// which is SUPER BAD.
if result == OK:
d.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
var f = d.get_next()
while f != "":
d.remove_at(f)
f = d.get_next()
did = true
return did
func delete_output_directory():
var did = clear_output_directory()
if did:
var d = Directory.new()
d.remove_at(_output_dir)
func add_ignored_method(path, method_name):
_ignored_methods.add(path, method_name)
func get_ignored_methods():
return _ignored_methods
func get_make_files():
return _make_files
func set_make_files(make_files):
_make_files = make_files
set_output_dir(_output_dir)
func get_method_maker():
return _method_maker
func add_ignored_method(obj, method_name):
_ignored_methods.add(obj, method_name)

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://c8axnpxc0nrk4"
path="res://.godot/imported/AnonymousPro-Bold.ttf-9d8fef4d357af5b52cd60afbe608aa49.fontdata"
[deps]
source_file="res://addons/gut/fonts/AnonymousPro-Bold.ttf"
dest_files=["res://.godot/imported/AnonymousPro-Bold.ttf-9d8fef4d357af5b52cd60afbe608aa49.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://msst1l2s2s"
path="res://.godot/imported/AnonymousPro-BoldItalic.ttf-4274bf704d3d6b9cd32c4f0754d8c83d.fontdata"
[deps]
source_file="res://addons/gut/fonts/AnonymousPro-BoldItalic.ttf"
dest_files=["res://.godot/imported/AnonymousPro-BoldItalic.ttf-4274bf704d3d6b9cd32c4f0754d8c83d.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://hf5rdg67jcwc"
path="res://.godot/imported/AnonymousPro-Italic.ttf-9989590b02137b799e13d570de2a42c1.fontdata"
[deps]
source_file="res://addons/gut/fonts/AnonymousPro-Italic.ttf"
dest_files=["res://.godot/imported/AnonymousPro-Italic.ttf-9989590b02137b799e13d570de2a42c1.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://c6c7gnx36opr0"
path="res://.godot/imported/AnonymousPro-Regular.ttf-856c843fd6f89964d2ca8d8ff1724f13.fontdata"
[deps]
source_file="res://addons/gut/fonts/AnonymousPro-Regular.ttf"
dest_files=["res://.godot/imported/AnonymousPro-Regular.ttf-856c843fd6f89964d2ca8d8ff1724f13.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://bhjgpy1dovmyq"
path="res://.godot/imported/CourierPrime-Bold.ttf-1f003c66d63ebed70964e7756f4fa235.fontdata"
[deps]
source_file="res://addons/gut/fonts/CourierPrime-Bold.ttf"
dest_files=["res://.godot/imported/CourierPrime-Bold.ttf-1f003c66d63ebed70964e7756f4fa235.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://n6mxiov5sbgc"
path="res://.godot/imported/CourierPrime-BoldItalic.ttf-65ebcc61dd5e1dfa8f96313da4ad7019.fontdata"
[deps]
source_file="res://addons/gut/fonts/CourierPrime-BoldItalic.ttf"
dest_files=["res://.godot/imported/CourierPrime-BoldItalic.ttf-65ebcc61dd5e1dfa8f96313da4ad7019.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://mcht266g817e"
path="res://.godot/imported/CourierPrime-Italic.ttf-baa9156a73770735a0f72fb20b907112.fontdata"
[deps]
source_file="res://addons/gut/fonts/CourierPrime-Italic.ttf"
dest_files=["res://.godot/imported/CourierPrime-Italic.ttf-baa9156a73770735a0f72fb20b907112.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://bnh0lslf4yh87"
path="res://.godot/imported/CourierPrime-Regular.ttf-3babe7e4a7a588dfc9a84c14b4f1fe23.fontdata"
[deps]
source_file="res://addons/gut/fonts/CourierPrime-Regular.ttf"
dest_files=["res://.godot/imported/CourierPrime-Regular.ttf-3babe7e4a7a588dfc9a84c14b4f1fe23.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://cmiuntu71oyl3"
path="res://.godot/imported/LobsterTwo-Bold.ttf-7c7f734103b58a32491a4788186f3dcb.fontdata"
[deps]
source_file="res://addons/gut/fonts/LobsterTwo-Bold.ttf"
dest_files=["res://.godot/imported/LobsterTwo-Bold.ttf-7c7f734103b58a32491a4788186f3dcb.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://bll38n2ct6qme"
path="res://.godot/imported/LobsterTwo-BoldItalic.ttf-227406a33e84448e6aa974176016de19.fontdata"
[deps]
source_file="res://addons/gut/fonts/LobsterTwo-BoldItalic.ttf"
dest_files=["res://.godot/imported/LobsterTwo-BoldItalic.ttf-227406a33e84448e6aa974176016de19.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://dis65h8wxc3f2"
path="res://.godot/imported/LobsterTwo-Italic.ttf-f93abf6c25390c85ad5fb6c4ee75159e.fontdata"
[deps]
source_file="res://addons/gut/fonts/LobsterTwo-Italic.ttf"
dest_files=["res://.godot/imported/LobsterTwo-Italic.ttf-f93abf6c25390c85ad5fb6c4ee75159e.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -0,0 +1,32 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://5e8msj0ih2pv"
path="res://.godot/imported/LobsterTwo-Regular.ttf-f3fcfa01cd671c8da433dd875d0fe04b.fontdata"
[deps]
source_file="res://addons/gut/fonts/LobsterTwo-Regular.ttf"
dest_files=["res://.godot/imported/LobsterTwo-Regular.ttf-f3fcfa01cd671c8da433dd875d0fe04b.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

94
addons/gut/fonts/OFL.txt Normal file
View file

@ -0,0 +1,94 @@
Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com),
with Reserved Font Name Anonymous Pro.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -1,6 +0,0 @@
# Since NativeScript does not exist if GDNative is not included in the build
# of Godot this script is conditionally loaded only when NativeScript exists.
# You can then get a reference to NativeScript for use in `is` checks by calling
# get_it.
static func get_it():
return NativeScript

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

File diff suppressed because it is too large Load diff

View file

@ -39,10 +39,11 @@
# ##############################################################################
extends SceneTree
var Optparse = load("res://addons/gut/optparse.gd")
var Gut = load("res://addons/gut/gut.gd")
var GutRunner = load("res://addons/gut/gui/GutRunner.tscn")
var Optparse = load('res://addons/gut/optparse.gd')
var Gut = load('res://addons/gut/gut.gd')
var GutRunner = load('res://addons/gut/gui/GutRunner.tscn')
var json = JSON.new()
# ------------------------------------------------------------------------------
# Helper class to resolve the various different places where an option can
@ -56,9 +57,10 @@ var GutRunner = load("res://addons/gut/gui/GutRunner.tscn")
# will punch through null values of higher precedented hashes.
# ------------------------------------------------------------------------------
class OptionResolver:
var base_opts = null
var cmd_opts = null
var config_opts = null
var base_opts = {}
var cmd_opts = {}
var config_opts = {}
func get_value(key):
return _nvl(cmd_opts[key], _nvl(config_opts[key], base_opts[key]))
@ -76,31 +78,21 @@ class OptionResolver:
return new_hash
func _nvl(a, b):
if a == null:
if(a == null):
return b
else:
return a
func _string_it(h):
var to_return = ""
var to_return = ''
for key in h:
to_return += str("(", key, ":", _nvl(h[key], "NULL"), ")")
to_return += str('(',key, ':', _nvl(h[key], 'NULL'), ')')
return to_return
func to_s():
return str(
"base:\n",
_string_it(base_opts),
"\n",
"config:\n",
_string_it(config_opts),
"\n",
"cmd:\n",
_string_it(cmd_opts),
"\n",
"resolved:\n",
_string_it(get_resolved_values())
)
return str("base:\n", _string_it(base_opts), "\n", \
"config:\n", _string_it(config_opts), "\n", \
"cmd:\n", _string_it(cmd_opts), "\n", \
"resolved:\n", _string_it(get_resolved_values()))
func get_resolved_values():
var to_return = {}
@ -109,24 +101,23 @@ class OptionResolver:
return to_return
func to_s_verbose():
var to_return = ""
var to_return = ''
var resolved = get_resolved_values()
for key in base_opts:
to_return += str(key, "\n")
to_return += str(" default: ", _nvl(base_opts[key], "NULL"), "\n")
to_return += str(" config: ", _nvl(config_opts[key], " --"), "\n")
to_return += str(" cmd: ", _nvl(cmd_opts[key], " --"), "\n")
to_return += str(" final: ", _nvl(resolved[key], "NULL"), "\n")
to_return += str(' default: ', _nvl(base_opts[key], 'NULL'), "\n")
to_return += str(' config: ', _nvl(config_opts[key], ' --'), "\n")
to_return += str(' cmd: ', _nvl(cmd_opts[key], ' --'), "\n")
to_return += str(' final: ', _nvl(resolved[key], 'NULL'), "\n")
return to_return
# ------------------------------------------------------------------------------
# Here starts the actual script that uses the Options class to kick off Gut
# and run your tests.
# ------------------------------------------------------------------------------
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _gut_config = load("res://addons/gut/gut_config.gd").new()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _gut_config = load('res://addons/gut/gut_config.gd').new()
# instance of gut
var _tester = null
# array of command line options specified
@ -135,173 +126,109 @@ var _final_opts = []
func setup_options(options, font_names):
var opts = Optparse.new()
opts.set_banner(
(
"This is the command line interface for the unit testing tool Gut. With this "
+ "interface you can run one or more test scripts from the command line. In order "
+ "for the Gut options to not clash with any other godot options, each option starts "
+ 'with a "g". Also, any option that requires a value will take the form of '
+ '"-g<name>=<value>". There cannot be any spaces between the option, the "=", or '
+ "inside a specified value or godot will think you are trying to run a scene."
)
)
opts.add("-gtest", [], "Comma delimited list of full paths to test scripts to run.")
opts.add("-gdir", options.dirs, "Comma delimited list of directories to add tests from.")
opts.add(
"-gprefix",
options.prefix,
'Prefix used to find tests when specifying -gdir. Default "[default]".'
)
opts.add(
"-gsuffix",
options.suffix,
'Suffix used to find tests when specifying -gdir. Default "[default]".'
)
opts.add(
"-ghide_orphans",
false,
'Display orphan counts for tests and scripts. Default "[default]".'
)
opts.add("-gmaximize", false, "Maximizes test runner window to fit the viewport.")
opts.add(
"-gcompact_mode", false, "The runner will be in compact mode. This overrides -gmaximize."
)
opts.add(
"-gexit",
false,
"Exit after running tests. If not specified you have to manually close the window."
)
opts.add("-gexit_on_success", false, "Only exit if all tests pass.")
opts.add("-glog", options.log_level, "Log level. Default [default]")
opts.add("-gignore_pause", false, "Ignores any calls to gut.pause_before_teardown.")
opts.add(
"-gselect",
"",
(
"Select a script to run initially. The first script that "
+ "was loaded using -gtest or -gdir that contains the specified "
+ "string will be executed. You may run others by interacting "
+ "with the GUI."
)
)
opts.add(
"-gunit_test_name",
"",
(
"Name of a test to run. Any test that contains the specified "
+ "text will be run, all others will be skipped."
)
)
opts.add("-gh", false, "Print this help, then quit")
opts.add(
"-gconfig",
"res://.gutconfig.json",
"A config file that contains configuration information. Default is res://.gutconfig.json"
)
opts.add("-ginner_class", "", "Only run inner classes that contain this string")
opts.add(
"-gopacity",
options.opacity,
"Set opacity of test runner window. Use range 0 - 100. 0 = transparent, 100 = opaque."
)
opts.add("-gpo", false, "Print option values from all sources and the value used, then quit.")
opts.add("-ginclude_subdirs", false, "Include subdirectories of -gdir.")
opts.add(
"-gdouble_strategy",
"partial",
'Default strategy to use when doubling. Valid values are [partial, full]. Default "[default]"'
)
opts.add("-gdisable_colors", false, "Disable command line colors.")
opts.add("-gpre_run_script", "", "pre-run hook script path")
opts.add("-gpost_run_script", "", "post-run hook script path")
opts.add(
"-gprint_gutconfig_sample",
false,
"Print out json that can be used to make a gutconfig file then quit."
)
opts.set_banner(('This is the command line interface for the unit testing tool Gut. With this ' +
'interface you can run one or more test scripts from the command line. In order ' +
'for the Gut options to not clash with any other godot options, each option starts ' +
'with a "g". Also, any option that requires a value will take the form of ' +
'"-g<name>=<value>". There cannot be any spaces between the option, the "=", or ' +
'inside a specified value or godot will think you are trying to run a scene.'))
opts.add(
"-gfont_name",
options.font_name,
str("Valid values are: ", font_names, '. Default "[default]"')
)
opts.add("-gfont_size", options.font_size, 'Font size, default "[default]"')
opts.add(
"-gbackground_color",
options.background_color,
'background color as an html color, default "[default]"'
)
opts.add("-gfont_color", options.font_color, 'Font color as an html color, default "[default]"')
opts.add('-gtest', [], 'Comma delimited list of full paths to test scripts to run.')
opts.add('-gdir', options.dirs, 'Comma delimited list of directories to add tests from.')
opts.add('-gprefix', options.prefix, 'Prefix used to find tests when specifying -gdir. Default "[default]".')
opts.add('-gsuffix', options.suffix, 'Test script suffix, including .gd extension. Default "[default]".')
opts.add('-ghide_orphans', false, 'Display orphan counts for tests and scripts. Default "[default]".')
opts.add('-gmaximize', false, 'Maximizes test runner window to fit the viewport.')
opts.add('-gcompact_mode', false, 'The runner will be in compact mode. This overrides -gmaximize.')
opts.add('-gexit', false, 'Exit after running tests. If not specified you have to manually close the window.')
opts.add('-gexit_on_success', false, 'Only exit if all tests pass.')
opts.add('-glog', options.log_level, 'Log level. Default [default]')
opts.add('-gignore_pause', false, 'Ignores any calls to gut.pause_before_teardown.')
opts.add('-gselect', '', ('Select a script to run initially. The first script that ' +
'was loaded using -gtest or -gdir that contains the specified ' +
'string will be executed. You may run others by interacting ' +
'with the GUI.'))
opts.add('-gunit_test_name', '', ('Name of a test to run. Any test that contains the specified ' +
'text will be run, all others will be skipped.'))
opts.add('-gh', false, 'Print this help, then quit')
opts.add('-gconfig', 'res://.gutconfig.json', 'A config file that contains configuration information. Default is res://.gutconfig.json')
opts.add('-ginner_class', '', 'Only run inner classes that contain this string')
opts.add('-gopacity', options.opacity, 'Set opacity of test runner window. Use range 0 - 100. 0 = transparent, 100 = opaque.')
opts.add('-gpo', false, 'Print option values from all sources and the value used, then quit.')
opts.add('-ginclude_subdirs', false, 'Include subdirectories of -gdir.')
opts.add('-gdouble_strategy', 'partial', 'Default strategy to use when doubling. Valid values are [partial, full]. Default "[default]"')
opts.add('-gdisable_colors', false, 'Disable command line colors.')
opts.add('-gpre_run_script', '', 'pre-run hook script path')
opts.add('-gpost_run_script', '', 'post-run hook script path')
opts.add('-gprint_gutconfig_sample', false, 'Print out json that can be used to make a gutconfig file then quit.')
opts.add(
"-gjunit_xml_file",
options.junit_xml_file,
"Export results of run to this file in the Junit XML format."
)
opts.add(
"-gjunit_xml_timestamp",
options.junit_xml_timestamp,
"Include a timestamp in the -gjunit_xml_file, default [default]"
)
opts.add('-gfont_name', options.font_name, str('Valid values are: ', font_names, '. Default "[default]"'))
opts.add('-gfont_size', options.font_size, 'Font size, default "[default]"')
opts.add('-gbackground_color', options.background_color, 'Background color as an html color, default "[default]"')
opts.add('-gfont_color',options.font_color, 'Font color as an html color, default "[default]"')
opts.add('-gpaint_after', options.paint_after, 'Delay before GUT will add a 1 frame pause to paint the screen/GUI. default [default]')
opts.add('-gjunit_xml_file', options.junit_xml_file, 'Export results of run to this file in the Junit XML format.')
opts.add('-gjunit_xml_timestamp', options.junit_xml_timestamp, 'Include a timestamp in the -gjunit_xml_file, default [default]')
return opts
# Parses options, applying them to the _tester or setting values
# in the options struct.
func extract_command_line_options(from, to):
to.config_file = from.get_value("-gconfig")
to.dirs = from.get_value("-gdir")
to.disable_colors = from.get_value("-gdisable_colors")
to.double_strategy = from.get_value("-gdouble_strategy")
to.ignore_pause = from.get_value("-gignore_pause")
to.include_subdirs = from.get_value("-ginclude_subdirs")
to.inner_class = from.get_value("-ginner_class")
to.log_level = from.get_value("-glog")
to.opacity = from.get_value("-gopacity")
to.post_run_script = from.get_value("-gpost_run_script")
to.pre_run_script = from.get_value("-gpre_run_script")
to.prefix = from.get_value("-gprefix")
to.selected = from.get_value("-gselect")
to.should_exit = from.get_value("-gexit")
to.should_exit_on_success = from.get_value("-gexit_on_success")
to.should_maximize = from.get_value("-gmaximize")
to.compact_mode = from.get_value("-gcompact_mode")
to.hide_orphans = from.get_value("-ghide_orphans")
to.suffix = from.get_value("-gsuffix")
to.tests = from.get_value("-gtest")
to.unit_test_name = from.get_value("-gunit_test_name")
to.config_file = from.get_value('-gconfig')
to.dirs = from.get_value('-gdir')
to.disable_colors = from.get_value('-gdisable_colors')
to.double_strategy = from.get_value('-gdouble_strategy')
to.ignore_pause = from.get_value('-gignore_pause')
to.include_subdirs = from.get_value('-ginclude_subdirs')
to.inner_class = from.get_value('-ginner_class')
to.log_level = from.get_value('-glog')
to.opacity = from.get_value('-gopacity')
to.post_run_script = from.get_value('-gpost_run_script')
to.pre_run_script = from.get_value('-gpre_run_script')
to.prefix = from.get_value('-gprefix')
to.selected = from.get_value('-gselect')
to.should_exit = from.get_value('-gexit')
to.should_exit_on_success = from.get_value('-gexit_on_success')
to.should_maximize = from.get_value('-gmaximize')
to.compact_mode = from.get_value('-gcompact_mode')
to.hide_orphans = from.get_value('-ghide_orphans')
to.suffix = from.get_value('-gsuffix')
to.tests = from.get_value('-gtest')
to.unit_test_name = from.get_value('-gunit_test_name')
to.font_size = from.get_value("-gfont_size")
to.font_name = from.get_value("-gfont_name")
to.background_color = from.get_value("-gbackground_color")
to.font_color = from.get_value("-gfont_color")
to.font_size = from.get_value('-gfont_size')
to.font_name = from.get_value('-gfont_name')
to.background_color = from.get_value('-gbackground_color')
to.font_color = from.get_value('-gfont_color')
to.paint_after = from.get_value('-gpaint_after')
to.junit_xml_file = from.get_value('-gjunit_xml_file')
to.junit_xml_timestamp = from.get_value('-gjunit_xml_timestamp')
to.junit_xml_file = from.get_value("-gjunit_xml_file")
to.junit_xml_timestamp = from.get_value("-gjunit_xml_timestamp")
func _print_gutconfigs(values):
var header = """Here is a sample of a full .gutconfig.json file.
var header = """Here is a sample of a full super.gutconfig.json file.
You do not need to specify all values in your own file. The values supplied in
this sample are what would be used if you ran gut w/o the -gprint_gutconfig_sample
option (option priority: command-line, super.gutconfig, default)."""
print("\n", header.replace("\n", " "), "\n\n")
print("\n", header.replace("\n", ' '), "\n\n")
var resolved = values
# remove_at some options that don't make sense to be in config
resolved.erase("config_file")
resolved.erase("show_help")
print(
"Here's a config with all the properties set based off of your current command and config."
)
print(JSON.stringify(resolved, " "))
print("Here's a config with all the properties set based off of your current command and config.")
print(json.stringify(resolved, ' '))
for key in resolved:
resolved[key] = null
print("\n\nAnd here's an empty config for you fill in what you want.")
print(JSON.stringify(resolved, " "))
print(json.stringify(resolved, ' '))
# parse options and run Gut
@ -309,89 +236,84 @@ func _run_gut():
var opt_resolver = OptionResolver.new()
opt_resolver.set_base_opts(_gut_config.default_options)
print("\n\n", " --- Gut ---")
print("\n\n", ' --- Gut ---')
var o = setup_options(_gut_config.default_options, _gut_config.valid_fonts)
var all_options_valid = o.parse()
extract_command_line_options(o, opt_resolver.cmd_opts)
var load_result = _gut_config.load_options_no_defaults(opt_resolver.get_value("config_file"))
var load_result = _gut_config.load_options_no_defaults(
opt_resolver.get_value('config_file'))
# SHORTCIRCUIT
if !all_options_valid or load_result == -1:
if(!all_options_valid or load_result == -1):
quit(1)
else:
opt_resolver.config_opts = _gut_config.options
if o.get_value("-gh"):
if(o.get_value('-gh')):
print(_utils.get_version_text())
o.print_help()
quit()
elif o.get_value("-gpo"):
print(
(
"All command line options and where they are specified. "
+ 'The "final" value shows which value will actually be used '
+ "based on order of precedence (default < super.gutconfig < cmd line)."
+ "\n"
)
)
elif(o.get_value('-gpo')):
print('All command line options and where they are specified. ' +
'The "final" value shows which value will actually be used ' +
'based on order of precedence (default < super.gutconfig < cmd line).' + "\n")
print(opt_resolver.to_s_verbose())
quit()
elif o.get_value("-gprint_gutconfig_sample"):
elif(o.get_value('-gprint_gutconfig_sample')):
_print_gutconfigs(opt_resolver.get_resolved_values())
quit()
else:
_final_opts = opt_resolver.get_resolved_values()
_final_opts = opt_resolver.get_resolved_values();
_gut_config.options = _final_opts
var runner = GutRunner.instantiate()
runner.set_cmdln_mode(true)
runner.set_gut_config(_gut_config)
_tester = runner.get_gut()
_tester.connect(
"tests_finished",
self,
"_on_tests_finished",
[_final_opts.should_exit, _final_opts.should_exit_on_success]
)
get_root().add_child(runner)
_tester = runner.get_gut()
_tester.connect('end_run', Callable(self,'_on_tests_finished').bind(_final_opts.should_exit, _final_opts.should_exit_on_success))
runner.run_tests()
# exit if option is set.
func _on_tests_finished(should_exit, should_exit_on_success):
if _final_opts.dirs.size() == 0:
if _tester.get_summary().get_totals().scripts == 0:
if(_final_opts.dirs.size() == 0):
if(_tester.get_summary().get_totals().scripts == 0):
var lgr = _tester.get_logger()
lgr.error(
"No directories configured. Add directories with options or a .gutconfig.json file. Use the -gh option for more information."
)
lgr.error('No directories configured. Add directories with options or a super.gutconfig.json file. Use the -gh option for more information.')
if _tester.get_fail_count():
OS.exit_code = 1
if(_tester.get_fail_count()):
set_exit_code(1)
# Overwrite the exit code with the post_script
var post_inst = _tester.get_post_run_script_instance()
if post_inst != null and post_inst.get_exit_code() != null:
OS.exit_code = post_inst.get_exit_code()
if(post_inst != null and post_inst.get_exit_code() != null):
set_exit_code(post_inst.get_exit_code())
if should_exit or (should_exit_on_success and _tester.get_fail_count() == 0):
if(should_exit or (should_exit_on_success and _tester.get_fail_count() == 0)):
quit()
else:
print("Tests finished, exit manually")
func set_exit_code(val):
pass
# OS.exit_code doesn't exist anymore, but when we find a solution it just
# goes here.
# ------------------------------------------------------------------------------
# MAIN
# ------------------------------------------------------------------------------
func _init():
if !_utils.is_version_ok():
if(!_utils.is_version_ok()):
print("\n\n", _utils.get_version_text())
push_error(_utils.get_bad_version_text())
OS.exit_code = 1
set_exit_code(1)
quit()
else:
_run_gut()

View file

@ -1,46 +1,60 @@
var Gut = load("res://addons/gut/gut.gd")
var Gut = load('res://addons/gut/gut.gd')
# Do not want a ref to _utils here due to use by editor plugin.
# _utils needs to be split so that constants and what not do not
# have to rely on the weird singleton thing I made.
enum DOUBLE_STRATEGY { FULL, PARTIAL }
enum DOUBLE_STRATEGY{
FULL,
PARTIAL
}
var valid_fonts = ["AnonymousPro", "CourierPro", "LobsterTwo", "Default"]
var valid_fonts = ['AnonymousPro', 'CourierPro', 'LobsterTwo', 'Default']
var default_options = {
background_color = Color(.15, .15, .15, 1).to_html(),
config_file = "res://.gutconfig.json",
config_file = 'res://.gutconfig.json',
dirs = [],
disable_colors = false,
double_strategy = "partial",
double_strategy = 'partial',
font_color = Color(.8, .8, .8, 1).to_html(),
font_name = "CourierPrime",
font_name = 'CourierPrime',
font_size = 16,
hide_orphans = false,
ignore_pause = false,
include_subdirs = false,
inner_class = "",
junit_xml_file = "",
inner_class = '',
junit_xml_file = '',
junit_xml_timestamp = false,
log_level = 1,
opacity = 100,
post_run_script = "",
pre_run_script = "",
prefix = "test_",
selected = "",
paint_after = .1,
post_run_script = '',
pre_run_script = '',
prefix = 'test_',
selected = '',
should_exit = false,
should_exit_on_success = false,
should_maximize = false,
compact_mode = false,
show_help = false,
suffix = ".gd",
suffix = '.gd',
tests = [],
unit_test_name = "",
unit_test_name = '',
gut_on_top = true,
}
var default_panel_options = {font_name = "CourierPrime", font_size = 30}
var default_panel_options = {
font_name = 'CourierPrime',
font_size = 30,
hide_result_tree = false,
hide_output_text = false,
hide_settings = false,
use_colors = true
}
var options = default_options.duplicate()
var json = JSON.new()
func _null_copy(h):
@ -52,72 +66,76 @@ func _null_copy(h):
func _load_options_from_config_file(file_path, into):
# SHORTCIRCUIT
var f = File.new()
if !f.file_exists(file_path):
if file_path != "res://.gutconfig.json":
if(!FileAccess.file_exists(file_path)):
if(file_path != 'res://.gutconfig.json'):
print('ERROR: Config File "', file_path, '" does not exist.')
return -1
else:
return 1
var result = f.open(file_path, f.READ)
if result != OK:
push_error(str("Could not load data ", file_path, " ", result))
var f = FileAccess.open(file_path, FileAccess.READ)
if(f == null):
var result = FileAccess.get_open_error()
push_error(str("Could not load data ", file_path, ' ', result))
return result
var json = f.get_as_text()
f.close()
f = null # close file
var test_json_conv = JSON.new()
test_json_conv.parse(json)
var results = test_json_conv.get_data()
# SHORTCIRCUIT
if results.error != OK:
print("\n\n", "!! ERROR parsing file: ", file_path)
print(" at line ", results.error_line, ":")
print(" ", results.error_string)
if(results == null):
print("\n\n",'!! ERROR parsing file: ', file_path)
print(' at line ', results.error_line, ':')
print(' ', results.error_string)
return -1
# Get all the options out of the config file using the option name. The
# options hash is now the default source of truth for the name of an option.
for key in into:
if results.result.has(key):
if results.result[key] != null:
into[key] = results.result[key]
_load_dict_into(results, into)
return 1
func _load_dict_into(source, dest):
for key in dest:
if(source.has(key)):
if(source[key] != null):
if(typeof(source[key]) == TYPE_DICTIONARY):
_load_dict_into(source[key], dest[key])
else:
dest[key] = source[key]
func write_options(path):
var content = JSON.stringify(options, " ")
var content = json.stringify(options, ' ')
var f = File.new()
var result = f.open(path, f.WRITE)
if result == OK:
var f = FileAccess.open(path, FileAccess.WRITE)
var result = FileAccess.get_open_error()
if(f != null):
f.store_string(content)
f.close()
else:
print('ERROR: could not open file ', path, ' ', result)
return result
# Apply all the options specified to _tester. This is where the rubber meets
# the road.
func _apply_options(opts, _tester):
_tester.set_yield_between_tests(true)
_tester.set_modulate(Color(1.0, 1.0, 1.0, min(1.0, float(opts.opacity) / 100)))
_tester.show()
_tester.include_subdirectories = opts.include_subdirs
_tester.set_include_subdirectories(opts.include_subdirs)
if(opts.inner_class != ''):
_tester.inner_class_name = opts.inner_class
_tester.log_level = opts.log_level
_tester.ignore_pause_before_teardown = opts.ignore_pause
if opts.should_maximize:
_tester.maximize()
if opts.compact_mode:
_tester.get_gui().compact_mode(true)
if opts.inner_class != "":
_tester.set_inner_class_name(opts.inner_class)
_tester.set_log_level(opts.log_level)
_tester.set_ignore_pause_before_teardown(opts.ignore_pause)
if(opts.selected != ''):
_tester.select_script(opts.selected)
for i in range(opts.dirs.size()):
_tester.add_directory(opts.dirs[i], opts.prefix, opts.suffix)
@ -125,29 +143,19 @@ func _apply_options(opts, _tester):
for i in range(opts.tests.size()):
_tester.add_script(opts.tests[i])
if opts.selected != "":
_tester.select_script(opts.selected)
# _run_single = true
if(opts.double_strategy == 'include super'):
_tester.double_strategy = DOUBLE_STRATEGY.INCLUDE_SUPER
elif(opts.double_strategy == 'script only'):
_tester.double_strategy = DOUBLE_STRATEGY.SCRIPT_ONLY
if opts.double_strategy == "full":
_tester.set_double_strategy(DOUBLE_STRATEGY.FULL)
elif opts.double_strategy == "partial":
_tester.set_double_strategy(DOUBLE_STRATEGY.PARTIAL)
_tester.set_unit_test_name(opts.unit_test_name)
_tester.set_pre_run_script(opts.pre_run_script)
_tester.set_post_run_script(opts.post_run_script)
_tester.set_color_output(!opts.disable_colors)
_tester.unit_test_name = opts.unit_test_name
_tester.pre_run_script = opts.pre_run_script
_tester.post_run_script = opts.post_run_script
_tester.color_output = !opts.disable_colors
_tester.show_orphans(!opts.hide_orphans)
_tester.set_junit_xml_file(opts.junit_xml_file)
_tester.set_junit_xml_timestamp(opts.junit_xml_timestamp)
_tester.get_gui().set_font_size(opts.font_size)
_tester.get_gui().set_font(opts.font_name)
if opts.font_color != null and opts.font_color.is_valid_html_color():
_tester.get_gui().set_default_font_color(Color(opts.font_color))
if opts.background_color != null and opts.background_color.is_valid_html_color():
_tester.get_gui().set_background_color(Color(opts.background_color))
_tester.junit_xml_file = opts.junit_xml_file
_tester.junit_xml_timestamp = opts.junit_xml_timestamp
_tester.paint_after = str(opts.paint_after).to_float()
return _tester
@ -159,16 +167,13 @@ func config_gut(gut):
func load_options(path):
return _load_options_from_config_file(path, options)
func load_panel_options(path):
options["panel_options"] = default_panel_options.duplicate()
options['panel_options'] = default_panel_options.duplicate()
return _load_options_from_config_file(path, options)
func load_options_no_defaults(path):
options = _null_copy(default_options)
return _load_options_from_config_file(path, options)
func apply_options(gut):
_apply_options(options, gut)

24
addons/gut/gut_plugin.gd Normal file
View file

@ -0,0 +1,24 @@
@tool
extends EditorPlugin
var _bottom_panel = null
func _enter_tree():
_bottom_panel = preload('res://addons/gut/gui/GutBottomPanel.tscn').instantiate()
var button = add_control_to_bottom_panel(_bottom_panel, 'GUT')
button.shortcut_in_tooltip = true
await get_tree().create_timer(3).timeout
_bottom_panel.set_interface(get_editor_interface())
_bottom_panel.set_plugin(self)
_bottom_panel.set_panel_button(button)
_bottom_panel.load_shortcuts()
func _exit_tree():
# Clean-up of the plugin goes here
# Always remember to remove_at it from the engine when deactivated
remove_control_from_bottom_panel(_bottom_panel)
_bottom_panel.free()

75
addons/gut/gut_to_move.gd Normal file
View file

@ -0,0 +1,75 @@
# Temporary base script for gut.gd to hold the things to be remvoed and added
# to some utility somewhere.
extends Node
var _utils = load('res://addons/gut/utils.gd').get_instance()
# ------------------------------------------------------------------------------
# deletes all files in a given directory
# ------------------------------------------------------------------------------
func directory_delete_files(path):
var d = DirAccess.open(path)
# SHORTCIRCUIT
if(d == null):
return
# Traversing a directory is kinda odd. You have to start the process of listing
# the contents of a directory with list_dir_begin then use get_next until it
# returns an empty string. Then I guess you should end it.
d.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
var thing = d.get_next() # could be a dir or a file or something else maybe?
var full_path = ''
while(thing != ''):
full_path = path + "/" + thing
#file_exists returns fasle for directories
if(d.file_exists(full_path)):
d.remove(full_path)
thing = d.get_next()
d.list_dir_end()
# ------------------------------------------------------------------------------
# deletes the file at the specified path
# ------------------------------------------------------------------------------
func file_delete(path):
var d = DirAccess.open(path.get_base_dir())
if(d != null):
d.remove(path)
# ------------------------------------------------------------------------------
# Checks to see if the passed in file has any data in it.
# ------------------------------------------------------------------------------
func is_file_empty(path):
var f = FileAccess.open(path, FileAccess.READ)
var result = FileAccess.get_open_error()
var empty = true
if(result == OK):
empty = f.get_length() == 0
f = null
return empty
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
func get_file_as_text(path):
return _utils.get_file_as_text(path)
# ------------------------------------------------------------------------------
# Creates an empty file at the specified path
# ------------------------------------------------------------------------------
func file_touch(path):
FileAccess.open(path, FileAccess.WRITE)
# ------------------------------------------------------------------------------
# Call _process or _fixed_process, if they exist, on obj and all it's children
# and their children and so and so forth. Delta will be passed through to all
# the _process or _fixed_process methods.
# ------------------------------------------------------------------------------
func simulate(obj, times, delta):
for _i in range(times):
if(obj.has_method("_process")):
obj._process(delta)
if(obj.has_method("_physics_process")):
obj._physics_process(delta)
for kid in obj.get_children():
simulate(kid, 1, delta)

View file

@ -5,43 +5,37 @@ class_name GutHookScript
#
# To use, inherit from this script and then implement the run method.
# ------------------------------------------------------------------------------
var JunitXmlExport = load("res://addons/gut/junit_xml_export.gd")
var JunitXmlExport = load('res://addons/gut/junit_xml_export.gd')
# This is the instance of GUT that is running the tests. You can get
# information about the run from this object. This is set by GUT when the
# script is instantiated.
var gut = null
var gut = null
# the exit code to be used by gut_cmdln. See set method.
var _exit_code = null
var _should_abort = false
var _should_abort = false
# Virtual method that will be called by GUT after instantiating
# this script.
func run():
gut.get_logger().error(
"Run method not overloaded. Create a 'run()' method in your hook script to run your code."
)
gut.logger.error("Run method not overloaded. Create a 'run()' method in your hook script to run your code.")
# Set the exit code when running from the command line. If not set then the
# default exit code will be returned (0 when no tests fail, 1 when any tests
# fail).
func set_exit_code(code):
_exit_code = code
_exit_code = code
func get_exit_code():
return _exit_code
# Usable by pre-run script to cause the run to end AFTER the run() method
# finishes. post-run script will not be ran.
func abort():
_should_abort = true
func should_abort():
return _should_abort

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 B

After

Width:  |  Height:  |  Size: 129 B

View file

@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://qo6k7ly05n3v"
uid="uid://bvo0uao7deu0q"
path="res://.godot/imported/icon.png-91b084043b8aaf2f1c906e7b9fa92969.ctex"
metadata={
"vram_texture": false

BIN
addons/gut/images/green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bvrnfjkcmpr8s"
path="res://.godot/imported/green.png-e3a17091688e10a7013279b38edc7f8a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/images/green.png"
dest_files=["res://.godot/imported/green.png-e3a17091688e10a7013279b38edc7f8a.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

BIN
addons/gut/images/red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ba2sgost7my3x"
path="res://.godot/imported/red.png-47a557c3922e800f76686bc1a4ad0c3c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/images/red.png"
dest_files=["res://.godot/imported/red.png-47a557c3922e800f76686bc1a4ad0c3c.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://o4mo2w2ftx1v"
path="res://.godot/imported/yellow.png-b3cf3d463958a169d909273d3d742052.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/images/yellow.png"
dest_files=["res://.godot/imported/yellow.png-b3cf3d463958a169d909273d3d742052.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,67 @@
var _registry = {}
func _create_reg_entry(base_path, subpath):
var to_return = {
"base_path":base_path,
"subpath":subpath,
"base_resource":load(base_path),
"full_path":str("'", base_path, "'", subpath)
}
return to_return
func _register_inners(base_path, obj, prev_inner = ''):
var const_map = obj.get_script_constant_map()
var consts = const_map.keys()
var const_idx = 0
while(const_idx < consts.size()):
var key = consts[const_idx]
var thing = const_map[key]
if(typeof(thing) == TYPE_OBJECT):
var cur_inner = str(prev_inner, ".", key)
_registry[thing] = _create_reg_entry(base_path, cur_inner)
_register_inners(base_path, thing, cur_inner)
const_idx += 1
func register(base_script):
var base_path = base_script.resource_path
_register_inners(base_path, base_script)
func get_extends_path(inner_class):
if(_registry.has(inner_class)):
return _registry[inner_class].full_path
else:
return null
# returns the subpath for the inner class. This includes the leading "." in
# the path.
func get_subpath(inner_class):
if(_registry.has(inner_class)):
return _registry[inner_class].subpath
else:
return ''
func get_base_path(inner_class):
if(_registry.has(inner_class)):
return _registry[inner_class].base_path
func has(inner_class):
return _registry.has(inner_class)
func get_base_resource(inner_class):
if(_registry.has(inner_class)):
return _registry[inner_class].base_resource
func to_s():
var text = ""
for key in _registry:
text += str(key, ": ", _registry[key], "\n")
return text

View file

@ -30,6 +30,7 @@
# -----------
# ##############################################################################
# Implemented InputEvent* convenience methods
# InputEventAction
# InputEventKey
@ -45,9 +46,10 @@
# InputEventScreenDrag
# InputEventScreenTouch
static func _to_scancode(which):
var key_code = which
if typeof(key_code) == TYPE_STRING:
if(typeof(key_code) == TYPE_STRING):
key_code = key_code.to_upper().to_ascii_buffer()[0]
return key_code
@ -55,9 +57,9 @@ static func _to_scancode(which):
static func new_mouse_button_event(position, global_position, pressed, button_index):
var event = InputEventMouseButton.new()
event.position = position
if global_position != null:
if(global_position != null):
event.global_position = global_position
event.button_pressed = pressed
event.pressed = pressed
event.button_index = button_index
return event
@ -65,76 +67,76 @@ static func new_mouse_button_event(position, global_position, pressed, button_in
static func key_up(which):
var event = InputEventKey.new()
event.scancode = _to_scancode(which)
event.button_pressed = false
event.keycode = _to_scancode(which)
event.pressed = false
return event
static func key_down(which):
var event = InputEventKey.new()
event.scancode = _to_scancode(which)
event.button_pressed = true
event.keycode = _to_scancode(which)
event.pressed = true
return event
static func action_up(which, strength = 1.0):
var event = InputEventAction.new()
static func action_up(which, strength=1.0):
var event = InputEventAction.new()
event.action = which
event.strength = strength
return event
static func action_down(which, strength = 1.0):
var event = InputEventAction.new()
static func action_down(which, strength=1.0):
var event = InputEventAction.new()
event.action = which
event.strength = strength
event.button_pressed = true
event.pressed = true
return event
static func mouse_left_button_down(position, global_position = null):
static func mouse_left_button_down(position, global_position=null):
var event = new_mouse_button_event(position, global_position, true, MOUSE_BUTTON_LEFT)
return event
static func mouse_left_button_up(position, global_position = null):
static func mouse_left_button_up(position, global_position=null):
var event = new_mouse_button_event(position, global_position, false, MOUSE_BUTTON_LEFT)
return event
static func mouse_double_click(position, global_position = null):
static func mouse_double_click(position, global_position=null):
var event = new_mouse_button_event(position, global_position, false, MOUSE_BUTTON_LEFT)
event.doubleclick = true
event.double_click = true
return event
static func mouse_right_button_down(position, global_position = null):
static func mouse_right_button_down(position, global_position=null):
var event = new_mouse_button_event(position, global_position, true, MOUSE_BUTTON_RIGHT)
return event
static func mouse_right_button_up(position, global_position = null):
static func mouse_right_button_up(position, global_position=null):
var event = new_mouse_button_event(position, global_position, false, MOUSE_BUTTON_RIGHT)
return event
static func mouse_motion(position, global_position = null):
static func mouse_motion(position, global_position=null):
var event = InputEventMouseMotion.new()
event.position = position
if global_position != null:
if(global_position != null):
event.global_position = global_position
return event
static func mouse_relative_motion(offset, last_motion_event = null, speed = Vector2(0, 0)):
static func mouse_relative_motion(offset, last_motion_event=null, speed=Vector2(0, 0)):
var event = null
if last_motion_event == null:
if(last_motion_event == null):
event = mouse_motion(offset)
event.speed = speed
event.velocity = speed
else:
event = last_motion_event.duplicate()
event.position += offset
event.global_position += offset
event.relative = offset
event.speed = speed
event.velocity = speed
return event

View file

@ -66,12 +66,12 @@ class InputQueueItem:
# TODO should this be done in _physics_process instead or should it be
# configurable?
func _physics_process(delta):
if frame_delay > 0 and _delay_started:
if(frame_delay > 0 and _delay_started):
_waited_frames += 1
if _waited_frames >= frame_delay:
if(_waited_frames >= frame_delay):
emit_signal("event_ready")
func _init(t_delay, f_delay):
func _init(t_delay,f_delay):
time_delay = t_delay
frame_delay = f_delay
_is_ready = time_delay == 0 and frame_delay == 0
@ -88,7 +88,7 @@ class InputQueueItem:
func start():
_delay_started = true
if time_delay > 0:
if(time_delay > 0):
var t = _delay_timer(time_delay)
t.connect("timeout",Callable(self,"_on_time_timeout"))
@ -96,10 +96,10 @@ class InputQueueItem:
# ##############################################################################
#
# ##############################################################################
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var InputFactory = load("res://addons/gut/input_factory.gd")
const INPUT_WARN = "If using Input as a reciever it will not respond to *_down events until a *_up event is recieved. Call the appropriate *_up event or use super.hold_for(...) to automatically release after some duration."
const INPUT_WARN = 'If using Input as a reciever it will not respond to *_down events until a *_up event is recieved. Call the appropriate *_up event or use hold_for(...) to automatically release after some duration.'
var _lgr = _utils.get_logger()
var _receivers = []
@ -111,72 +111,55 @@ var _last_mouse_motion = null
# used by hold_for and echo.
var _last_event = null
# indexed by scancode, each entry contains a boolean value indicating the
# last emitted "pressed" value for that scancode.
# indexed by keycode, each entry contains a boolean value indicating the
# last emitted "pressed" value for that keycode.
var _pressed_keys = {}
var _pressed_actions = {}
var _pressed_mouse_buttons = {}
var _auto_flush_input = false
signal idle
func _init(r = null):
if r != null:
func _init(r=null):
if(r != null):
add_receiver(r)
func _send_event(event):
if event is InputEventKey:
if (event.pressed and !event.echo) and is_key_pressed(event.scancode):
_lgr.warn(
str(
"InputSender: key_down called for ",
event.as_text(),
" when that key is already pressed. ",
INPUT_WARN
)
)
_pressed_keys[event.scancode] = event.pressed
elif event is InputEventAction:
if event.pressed and is_action_pressed(event.action):
_lgr.warn(
str(
"InputSender: action_down called for ",
event.action,
" when that action is already pressed. ",
INPUT_WARN
)
)
if(event is InputEventKey):
if((event.pressed and !event.echo) and is_key_pressed(event.keycode)):
_lgr.warn(str("InputSender: key_down called for ", event.as_text(), " when that key is already pressed. ", INPUT_WARN))
_pressed_keys[event.keycode] = event.pressed
elif(event is InputEventAction):
if(event.pressed and is_action_pressed(event.action)):
_lgr.warn(str("InputSender: action_down called for ", event.action, " when that action is already pressed. ", INPUT_WARN))
_pressed_actions[event.action] = event.pressed
elif event is InputEventMouseButton:
if event.pressed and is_mouse_button_pressed(event.button_index):
_lgr.warn(
str(
"InputSender: mouse_button_down called for ",
event.button_index,
" when that mouse button is already pressed. ",
INPUT_WARN
)
)
elif(event is InputEventMouseButton):
if(event.pressed and is_mouse_button_pressed(event.button_index)):
_lgr.warn(str("InputSender: mouse_button_down called for ", event.button_index, " when that mouse button is already pressed. ", INPUT_WARN))
_pressed_mouse_buttons[event.button_index] = event
for r in _receivers:
if r == Input:
if(r == Input):
Input.parse_input_event(event)
if(_auto_flush_input):
Input.flush_buffered_events()
else:
if r.has_method("_input"):
if(r.has_method("_input")):
r._input(event)
if r.has_method("_gui_input"):
if(r.has_method("_gui_input")):
r._gui_input(event)
if r.has_method("_unhandled_input"):
if(r.has_method("_unhandled_input")):
r._unhandled_input(event)
func _send_or_record_event(event):
_last_event = event
if _next_queue_item != null:
if(_next_queue_item != null):
_next_queue_item.events.append(event)
else:
_send_event(event)
@ -189,7 +172,7 @@ func _on_queue_item_ready(item):
var done_event = _input_queue.pop_front()
done_event.queue_free()
if _input_queue.size() == 0:
if(_input_queue.size() == 0):
_next_queue_item = null
emit_signal("idle")
else:
@ -197,11 +180,11 @@ func _on_queue_item_ready(item):
func _add_queue_item(item):
item.connect("event_ready",Callable(self,"_on_queue_item_ready").bind(item))
item.connect("event_ready", _on_queue_item_ready.bind(item))
_next_queue_item = item
_input_queue.append(item)
Engine.get_main_loop().root.add_child(item)
if _input_queue.size() == 1:
if(_input_queue.size() == 1):
item.start()
@ -214,13 +197,13 @@ func get_receivers():
func wait(t):
if typeof(t) == TYPE_STRING:
var suffix = t.substr(t.length() - 1, 1)
var val = float(t.rstrip("s").rstrip("f"))
if(typeof(t) == TYPE_STRING):
var suffix = t.substr(t.length() -1, 1)
var val = t.rstrip('s').rstrip('f').to_float()
if suffix.to_lower() == "s":
if(suffix.to_lower() == 's'):
wait_secs(val)
elif suffix.to_lower() == "f":
elif(suffix.to_lower() == 'f'):
wait_frames(val)
else:
wait_secs(t)
@ -256,71 +239,71 @@ func key_down(which):
func key_echo():
if _last_event != null and _last_event is InputEventKey:
if(_last_event != null and _last_event is InputEventKey):
var new_key = _last_event.duplicate()
new_key.echo = true
_send_or_record_event(new_key)
return self
func action_up(which, strength = 1.0):
var event = InputFactory.action_up(which, strength)
func action_up(which, strength=1.0):
var event = InputFactory.action_up(which, strength)
_send_or_record_event(event)
return self
func action_down(which, strength = 1.0):
var event = InputFactory.action_down(which, strength)
func action_down(which, strength=1.0):
var event = InputFactory.action_down(which, strength)
_send_or_record_event(event)
return self
func mouse_left_button_down(position, global_position = null):
func mouse_left_button_down(position, global_position=null):
var event = InputFactory.mouse_left_button_down(position, global_position)
_send_or_record_event(event)
return self
func mouse_left_button_up(position, global_position = null):
func mouse_left_button_up(position, global_position=null):
var event = InputFactory.mouse_left_button_up(position, global_position)
_send_or_record_event(event)
return self
func mouse_double_click(position, global_position = null):
func mouse_double_click(position, global_position=null):
var event = InputFactory.mouse_double_click(position, global_position)
event.doubleclick = true
event.double_click = true
_send_or_record_event(event)
return self
func mouse_right_button_down(position, global_position = null):
func mouse_right_button_down(position, global_position=null):
var event = InputFactory.mouse_right_button_down(position, global_position)
_send_or_record_event(event)
return self
func mouse_right_button_up(position, global_position = null):
func mouse_right_button_up(position, global_position=null):
var event = InputFactory.mouse_right_button_up(position, global_position)
_send_or_record_event(event)
return self
func mouse_motion(position, global_position = null):
func mouse_motion(position, global_position=null):
var event = InputFactory.mouse_motion(position, global_position)
_last_mouse_motion = event
_send_or_record_event(event)
return self
func mouse_relative_motion(offset, speed = Vector2(0, 0)):
func mouse_relative_motion(offset, speed=Vector2(0, 0)):
var event = InputFactory.mouse_relative_motion(offset, _last_mouse_motion, speed)
_last_mouse_motion = event
_send_or_record_event(event)
return self
func mouse_set_position(position, global_position = null):
func mouse_set_position(position, global_position=null):
_last_mouse_motion = InputFactory.mouse_motion(position, global_position)
return self
@ -332,27 +315,27 @@ func send_event(event):
func release_all():
for key in _pressed_keys:
if _pressed_keys[key]:
if(_pressed_keys[key]):
_send_event(InputFactory.key_up(key))
_pressed_keys.clear()
for key in _pressed_actions:
if _pressed_actions[key]:
if(_pressed_actions[key]):
_send_event(InputFactory.action_up(key))
_pressed_actions.clear()
for key in _pressed_mouse_buttons:
var event = _pressed_mouse_buttons[key].duplicate()
if event.pressed:
event.button_pressed = false
if(event.pressed):
event.pressed = false
_send_event(event)
_pressed_mouse_buttons.clear()
func hold_for(duration):
if _last_event != null and _last_event.pressed:
if(_last_event != null and _last_event.pressed):
var next_event = _last_event.duplicate()
next_event.button_pressed = false
next_event.pressed = false
wait(duration)
send_event(next_event)
return self
@ -373,19 +356,23 @@ func clear():
_pressed_actions.clear()
_pressed_mouse_buttons.clear()
func is_idle():
return _input_queue.size() == 0
func is_key_pressed(which):
var event = InputFactory.key_up(which)
return _pressed_keys.has(event.scancode) and _pressed_keys[event.scancode]
return _pressed_keys.has(event.keycode) and _pressed_keys[event.keycode]
func is_action_pressed(which):
return _pressed_actions.has(which) and _pressed_actions[which]
func is_mouse_button_pressed(which):
return _pressed_mouse_buttons.has(which) and _pressed_mouse_buttons[which]
func get_auto_flush_input():
return _auto_flush_input
func set_auto_flush_input(val):
_auto_flush_input = val

View file

@ -1,11 +1,10 @@
# ------------------------------------------------------------------------------
# Creates an export of a test run in the JUnit XML format.
# ------------------------------------------------------------------------------
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _exporter = _utils.ResultExporter.new()
func indent(s, ind):
var to_return = ind + s
to_return = to_return.replace("\n", "\n" + ind)
@ -15,17 +14,16 @@ func indent(s, ind):
func add_attr(name, value):
return str(name, '="', value, '" ')
func _export_test_result(test):
var to_return = ""
var to_return = ''
# Right now the pending and failure messages won't fit in the message
# attribute because they can span multiple lines and need to be escaped.
if test.status == "pending":
var skip_tag = str('<skipped message="pending">', test.pending[0], "</skipped>")
if(test.status == 'pending'):
var skip_tag = str("<skipped message=\"pending\">", test.pending[0], "</skipped>")
to_return += skip_tag
elif test.status == "fail":
var fail_tag = str('<failure message="failed">', test.failing[0], "</failure>")
elif(test.status == 'fail'):
var fail_tag = str("<failure message=\"failed\">", test.failing[0], "</failure>")
to_return += fail_tag
return to_return
@ -72,15 +70,15 @@ func _export_scripts(exp_results):
func get_results_xml(gut):
var exp_results = _exporter.get_results_dictionary(gut)
var to_return = '<?xml version="1.0" encoding="UTF-8"?>' + "\n"
to_return += "<testsuites "
to_return += add_attr("name", "GutTests")
to_return += '<testsuites '
to_return += add_attr("name", 'GutTests')
to_return += add_attr("failures", exp_results.test_scripts.props.failures)
to_return += add_attr("tests", exp_results.test_scripts.props.tests)
to_return += add_attr('tests', exp_results.test_scripts.props.tests)
to_return += ">\n"
to_return += indent(_export_scripts(exp_results), " ")
to_return += "</testsuites>"
to_return += '</testsuites>'
return to_return
@ -88,8 +86,9 @@ func write_file(gut, path):
var xml = get_results_xml(gut)
var f_result = _utils.write_file(path, xml)
if f_result != OK:
if(f_result != OK):
var msg = str("Error: ", f_result, ". Could not create export file ", path)
_utils.get_logger().error(msg)
return f_result

View file

@ -30,38 +30,40 @@
# various message types (error, warning, etc).
# ##############################################################################
var types = {
debug = "debug",
deprecated = "deprecated",
error = "error",
failed = "failed",
info = "info",
normal = "normal",
orphan = "orphan",
passed = "passed",
pending = "pending",
warn = "warn",
debug = 'debug',
deprecated = 'deprecated',
error = 'error',
failed = 'failed',
info = 'info',
normal = 'normal',
orphan = 'orphan',
passed = 'passed',
pending = 'pending',
warn ='warn',
}
var fmts = {
red = "red",
yellow = "yellow",
green = "green",
bold = "bold",
underline = "underline",
red = 'red',
yellow = 'yellow',
green = 'green',
bold = 'bold',
underline = 'underline',
none = null
}
var _type_data = {
types.debug: {disp = "DEBUG", enabled = true, fmt = fmts.none},
types.deprecated: {disp = "DEPRECATED", enabled = true, fmt = fmts.none},
types.error: {disp = "ERROR", enabled = true, fmt = fmts.red},
types.failed: {disp = "Failed", enabled = true, fmt = fmts.red},
types.info: {disp = "INFO", enabled = true, fmt = fmts.bold},
types.normal: {disp = "NORMAL", enabled = true, fmt = fmts.none},
types.orphan: {disp = "Orphans", enabled = true, fmt = fmts.yellow},
types.passed: {disp = "Passed", enabled = true, fmt = fmts.green},
types.pending: {disp = "Pending", enabled = true, fmt = fmts.yellow},
types.warn: {disp = "WARNING", enabled = true, fmt = fmts.yellow},
types.debug: {disp='DEBUG', enabled=true, fmt=fmts.none},
types.deprecated: {disp='DEPRECATED', enabled=true, fmt=fmts.none},
types.error: {disp='ERROR', enabled=true, fmt=fmts.red},
types.failed: {disp='Failed', enabled=true, fmt=fmts.red},
types.info: {disp='INFO', enabled=true, fmt=fmts.bold},
types.normal: {disp='NORMAL', enabled=true, fmt=fmts.none},
types.orphan: {disp='Orphans', enabled=true, fmt=fmts.yellow},
types.passed: {disp='Passed', enabled=true, fmt=fmts.green},
types.pending: {disp='Pending', enabled=true, fmt=fmts.yellow},
types.warn: {disp='WARNING', enabled=true, fmt=fmts.yellow},
}
var _logs = {
@ -72,20 +74,24 @@ var _logs = {
types.deprecated: [],
}
var _printers = {terminal = null, gui = null, console = null}
var _printers = {
terminal = null,
gui = null,
console = null
}
var _gut = null
var _utils = null
var _indent_level = 0
var _indent_string = " "
var _indent_string = ' '
var _skip_test_name_for_testing = false
var _less_test_names = false
var _yield_calls = 0
var _last_yield_text = ""
var _last_yield_text = ''
func _init():
_utils = load("res://addons/gut/utils.gd").get_instance()
_utils = load('res://addons/gut/utils.gd').get_instance()
_printers.terminal = _utils.Printers.TerminalPrinter.new()
_printers.console = _utils.Printers.ConsolePrinter.new()
# There were some problems in the timing of disabling this at the right
@ -93,22 +99,20 @@ func _init():
# by plugin_control.gd based on settings.
_printers.console.set_disabled(true)
func get_indent_text():
var pad = ""
var pad = ''
for i in range(_indent_level):
pad += _indent_string
return pad
func _indent_text(text):
var to_return = text
var ending_newline = ""
var ending_newline = ''
if text.ends_with("\n"):
if(text.ends_with("\n")):
ending_newline = "\n"
to_return = to_return.left(to_return.length() - 1)
to_return = to_return.left(to_return.length() -1)
var pad = get_indent_text()
to_return = to_return.replace("\n", "\n" + pad)
@ -116,276 +120,246 @@ func _indent_text(text):
return pad + to_return
func _should_print_to_printer(key_name):
return _printers[key_name] != null and !_printers[key_name].get_disabled()
func _print_test_name():
if _gut == null:
if(_gut == null):
return
var cur_test = _gut.get_current_test_object()
if cur_test == null:
if(cur_test == null):
return false
if !cur_test.has_printed_name:
_output("* " + cur_test.name + "\n")
if(!cur_test.has_printed_name):
_output('* ' + cur_test.name + "\n")
cur_test.has_printed_name = true
func _output(text, fmt = null):
func _output(text, fmt=null):
for key in _printers:
if _should_print_to_printer(key):
var info = "" #str(self, ':', key, ':', _printers[key], '| ')
if(_should_print_to_printer(key)):
var info = ''#str(self, ':', key, ':', _printers[key], '| ')
_printers[key].send(info + text, fmt)
func _log(text, fmt = fmts.none):
func _log(text, fmt=fmts.none):
_print_test_name()
var indented = _indent_text(text)
_output(indented, fmt)
# ---------------
# Get Methods
# ---------------
func get_warnings():
return get_log_entries(types.warn)
func get_errors():
return get_log_entries(types.error)
func get_infos():
return get_log_entries(types.info)
func get_debugs():
return get_log_entries(types.debug)
func get_deprecated():
return get_log_entries(types.deprecated)
func get_count(log_type = null):
func get_count(log_type=null):
var count = 0
if log_type == null:
if(log_type == null):
for key in _logs:
count += _logs[key].size()
else:
count = _logs[log_type].size()
return count
func get_log_entries(log_type):
return _logs[log_type]
# ---------------
# Log methods
# ---------------
func _output_type(type, text):
var td = _type_data[type]
if !td.enabled:
if(!td.enabled):
return
_print_test_name()
if type != types.normal:
if _logs.has(type):
if(type != types.normal):
if(_logs.has(type)):
_logs[type].append(text)
var start = str("[", td.disp, "]")
if text != null and text != "":
start += ": "
var start = str('[', td.disp, ']')
if(text != null and text != ''):
start += ': '
else:
start += " "
start += ' '
var indented_start = _indent_text(start)
var indented_end = _indent_text(text)
indented_end = indented_end.lstrip(_indent_string)
_output(indented_start, td.fmt)
_output(indented_end + "\n")
func debug(text):
_output_type(types.debug, text)
# supply some text or the name of the deprecated method and the replacement.
func deprecated(text, alt_method = null):
func deprecated(text, alt_method=null):
var msg = text
if alt_method:
msg = str("The method ", text, " is deprecated, use ", alt_method, " instead.")
if(alt_method):
msg = str('The method ', text, ' is deprecated, use ', alt_method , ' instead.')
return _output_type(types.deprecated, msg)
func error(text):
_output_type(types.error, text)
func failed(text):
_output_type(types.failed, text)
func info(text):
_output_type(types.info, text)
func orphan(text):
_output_type(types.orphan, text)
func passed(text):
_output_type(types.passed, text)
func pending(text):
_output_type(types.pending, text)
func warn(text):
_output_type(types.warn, text)
func log(text = "", fmt = fmts.none):
func log(text='', fmt=fmts.none):
end_yield()
if text == "":
if(text == ''):
_output("\n")
else:
_log(text + "\n", fmt)
return null
func lograw(text, fmt = fmts.none):
func lograw(text, fmt=fmts.none):
return _output(text, fmt)
# Print the test name if we aren't skipping names of tests that pass (basically
# what _less_test_names means))
func log_test_name():
# suppress output if we haven't printed the test name yet and
# what to print is the test name.
if !_less_test_names:
if(!_less_test_names):
_print_test_name()
# ---------------
# Misc
# ---------------
func get_gut():
return _gut
func set_gut(gut):
_gut = gut
if _gut == null:
if(_gut == null):
_printers.gui = null
else:
if _printers.gui == null:
if(_printers.gui == null):
_printers.gui = _utils.Printers.GutGuiPrinter.new()
_printers.gui.set_gut(gut)
func get_indent_level():
return _indent_level
func set_indent_level(indent_level):
_indent_level = indent_level
func get_indent_string():
return _indent_string
func set_indent_string(indent_string):
_indent_string = indent_string
func clear():
for key in _logs:
_logs[key].clear()
func inc_indent():
_indent_level += 1
func dec_indent():
_indent_level = max(0, _indent_level - 1)
_indent_level = max(0, _indent_level -1)
func is_type_enabled(type):
return _type_data[type].enabled
func set_type_enabled(type, is_enabled):
_type_data[type].enabled = is_enabled
func get_less_test_names():
return _less_test_names
func set_less_test_names(less_test_names):
_less_test_names = less_test_names
func disable_printer(name, is_disabled):
_printers[name].set_disabled(is_disabled)
func is_printer_disabled(name):
return _printers[name].get_disabled()
func disable_formatting(is_disabled):
for key in _printers:
_printers[key].set_format_enabled(!is_disabled)
func disable_all_printers(is_disabled):
for p in _printers:
disable_printer(p, is_disabled)
func get_printer(printer_key):
return _printers[printer_key]
func _yield_text_terminal(text):
var printer = _printers["terminal"]
if _yield_calls != 0:
var printer = _printers['terminal']
if(_yield_calls != 0):
printer.clear_line()
printer.back(_last_yield_text.length())
printer.send(text, fmts.yellow)
func _end_yield_terminal():
var printer = _printers["terminal"]
var printer = _printers['terminal']
printer.clear_line()
printer.back(_last_yield_text.length())
func _yield_text_gui(text):
var lbl = _gut.get_gui().get_waiting_label()
lbl.visible = true
lbl.set_bbcode("[color=yellow]" + text + "[/color]")
pass
# var lbl = _gut.get_gui().get_waiting_label()
# lbl.visible = true
# lbl.set_bbcode('[color=yellow]' + text + '[/color]')
func _end_yield_gui():
var lbl = _gut.get_gui().get_waiting_label()
lbl.visible = false
lbl.set_text("")
pass
# var lbl = _gut.get_gui().get_waiting_label()
# lbl.visible = false
# lbl.set_text('')
# This is used for displaying the "yield detected" and "yielding to" messages.
func yield_msg(text):
if(_type_data.warn.enabled):
self.log(text, fmts.yellow)
# This is used for the animated "waiting" message
func yield_text(text):
_yield_text_terminal(text)
_yield_text_gui(text)
_last_yield_text = text
_yield_calls += 1
# This is used for the animated "waiting" message
func end_yield():
if _yield_calls == 0:
if(_yield_calls == 0):
return
_end_yield_terminal()
_end_yield_gui()
_yield_calls = 0
_last_yield_text = ""
_last_yield_text = ''
func get_gui_bbcode():
return _printers.gui.get_bbcode()

View file

@ -2,7 +2,7 @@ class CallParameters:
var p_name = null
var default = null
func _init(n, d):
func _init(n,d):
p_name = n
default = d
@ -26,9 +26,9 @@ class CallParameters:
# }]
# default_args []
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _lgr = _utils.get_logger()
const PARAM_PREFIX = "p_"
const PARAM_PREFIX = 'p_'
# ------------------------------------------------------
# _supported_defaults
@ -40,24 +40,26 @@ const PARAM_PREFIX = "p_"
# but things like Vectors and Colors do since only the parameters to create a
# new Vector or Color are included in the metadata.
# ------------------------------------------------------
# TYPE_NIL = 0 — Variable is of type nil (only applied for null).
# TYPE_BOOL = 1 — Variable is of type bool.
# TYPE_INT = 2 — Variable is of type int.
# TYPE_FLOAT = 3 — Variable is of type float/real.
# TYPE_STRING = 4 — Variable is of type String.
# TYPE_VECTOR2 = 5 — Variable is of type Vector2.
# TYPE_RECT2 = 6 — Variable is of type Rect2.
# TYPE_VECTOR3 = 7 — Variable is of type Vector3.
# TYPE_COLOR = 14 — Variable is of type Color.
# TYPE_OBJECT = 17 — Variable is of type Object.
# TYPE_DICTIONARY = 18 — Variable is of type Dictionary.
# TYPE_ARRAY = 19 — Variable is of type Array.
# TYPE_PACKED_VECTOR2_ARRAY = 24 — Variable is of type PackedVector2Array.
# TYPE_TRANSFORM3D = 13 — Variable is of type Transform3D.
# TYPE_TRANSFORM2D = 8 — Variable is of type Transform2D.
# TYPE_RID = 16 — Variable is of type RID.
# TYPE_PACKED_INT32_ARRAY = 21 — Variable is of type PackedInt32Array.
# TYPE_PACKED_FLOAT32_ARRAY = 22 — Variable is of type PackedFloat32Array.
# TYPE_NIL = 0 — Variable is of type nil (only applied for null).
# TYPE_BOOL = 1 — Variable is of type bool.
# TYPE_INT = 2 — Variable is of type int.
# TYPE_FLOAT = 3 — Variable is of type float/real.
# TYPE_STRING = 4 — Variable is of type String.
# TYPE_VECTOR2 = 5 — Variable is of type Vector2.
# TYPE_RECT2 = 6 — Variable is of type Rect2.
# TYPE_VECTOR3 = 7 — Variable is of type Vector3.
# TYPE_COLOR = 14 — Variable is of type Color.
# TYPE_OBJECT = 17 — Variable is of type Object.
# TYPE_DICTIONARY = 18 — Variable is of type Dictionary.
# TYPE_ARRAY = 19 — Variable is of type Array.
# TYPE_PACKED_VECTOR2_ARRAY = 24 — Variable is of type PackedVector2Array.
# TYPE_TRANSFORM3D = 13 — Variable is of type Transform3D.
# TYPE_TRANSFORM2D = 8 — Variable is of type Transform2D.
# TYPE_RID = 16 — Variable is of type RID.
# TYPE_PACKED_INT32_ARRAY = 21 — Variable is of type PackedInt32Array.
# TYPE_PACKED_FLOAT32_ARRAY = 22 — Variable is of type PackedFloat32Array.
# TYPE_PACKED_STRING_ARRAY = 23 — Variable is of type PackedStringArray.
# TYPE_PLANE = 9 — Variable is of type Plane.
# TYPE_QUATERNION = 10 — Variable is of type Quaternion.
@ -65,54 +67,54 @@ const PARAM_PREFIX = "p_"
# TYPE_BASIS = 12 — Variable is of type Basis.
# TYPE_NODE_PATH = 15 — Variable is of type NodePath.
# TYPE_PACKED_BYTE_ARRAY = 20 — Variable is of type PackedByteArray.
# TYPE_PACKED_STRING_ARRAY = 23 — Variable is of type PackedStringArray.
# TYPE_PACKED_VECTOR3_ARRAY = 25 — Variable is of type PackedVector3Array.
# TYPE_PACKED_COLOR_ARRAY = 26 — Variable is of type PackedColorArray.
# TYPE_MAX = 27 — Marker for end of type constants.
# ------------------------------------------------------
var _supported_defaults = []
func _init():
for _i in range(TYPE_MAX):
_supported_defaults.append(null)
# These types do not require a prefix for defaults
_supported_defaults[TYPE_NIL] = ""
_supported_defaults[TYPE_BOOL] = ""
_supported_defaults[TYPE_INT] = ""
_supported_defaults[TYPE_FLOAT] = ""
_supported_defaults[TYPE_OBJECT] = ""
_supported_defaults[TYPE_ARRAY] = ""
_supported_defaults[TYPE_STRING] = ""
_supported_defaults[TYPE_DICTIONARY] = ""
_supported_defaults[TYPE_PACKED_VECTOR2_ARRAY] = ""
_supported_defaults[TYPE_RID] = ""
_supported_defaults[TYPE_NIL] = ''
_supported_defaults[TYPE_BOOL] = ''
_supported_defaults[TYPE_INT] = ''
_supported_defaults[TYPE_FLOAT] = ''
_supported_defaults[TYPE_OBJECT] = ''
_supported_defaults[TYPE_ARRAY] = ''
_supported_defaults[TYPE_STRING] = ''
_supported_defaults[TYPE_STRING_NAME] = ''
_supported_defaults[TYPE_DICTIONARY] = ''
_supported_defaults[TYPE_PACKED_VECTOR2_ARRAY] = ''
_supported_defaults[TYPE_RID] = ''
# These require a prefix for whatever default is provided
_supported_defaults[TYPE_VECTOR2] = "Vector2"
_supported_defaults[TYPE_RECT2] = "Rect2"
_supported_defaults[TYPE_VECTOR3] = "Vector3"
_supported_defaults[TYPE_COLOR] = "Color"
_supported_defaults[TYPE_TRANSFORM2D] = "Transform2D"
_supported_defaults[TYPE_TRANSFORM3D] = "Transform3D"
_supported_defaults[TYPE_PACKED_INT32_ARRAY] = "PackedInt32Array"
_supported_defaults[TYPE_PACKED_FLOAT32_ARRAY] = "PackedFloat32Array"
_supported_defaults[TYPE_VECTOR2] = 'Vector2'
_supported_defaults[TYPE_VECTOR2I] = 'Vector2i'
_supported_defaults[TYPE_RECT2] = 'Rect2'
_supported_defaults[TYPE_RECT2I] = 'Rect2i'
_supported_defaults[TYPE_VECTOR3] = 'Vector3'
_supported_defaults[TYPE_COLOR] = 'Color'
_supported_defaults[TYPE_TRANSFORM2D] = 'Transform2D'
_supported_defaults[TYPE_TRANSFORM3D] = 'Transform3D'
_supported_defaults[TYPE_PACKED_INT32_ARRAY] = 'PackedInt32Array'
_supported_defaults[TYPE_PACKED_FLOAT32_ARRAY] = 'PackedFloat32Array'
_supported_defaults[TYPE_PACKED_STRING_ARRAY] = 'PackedStringArray'
# ###############
# Private
# ###############
var _func_text = _utils.get_file_as_text("res://addons/gut/double_templates/function_template.txt")
var _func_text = _utils.get_file_as_text('res://addons/gut/double_templates/function_template.txt')
var _init_text = _utils.get_file_as_text('res://addons/gut/double_templates/init_template.txt')
func _is_supported_default(type_flag):
return type_flag >= 0 and type_flag < _supported_defaults.size() and [type_flag] != null
return type_flag >= 0 and type_flag < _supported_defaults.size() and _supported_defaults[type_flag] != null
func _make_stub_default(method, index):
return str('__gut_default_val("', method, '",', index, ")")
return str('__gutdbl.default_val("', method, '",', index, ')')
func _make_arg_array(method_meta, override_size):
var to_return = []
@ -122,88 +124,18 @@ func _make_arg_array(method_meta, override_size):
for i in range(method_meta.args.size()):
var pname = method_meta.args[i].name
var dflt_text = ""
if i < dflt_start:
dflt_text = _make_stub_default(method_meta.name, i)
else:
var dflt_idx = i - dflt_start
var t = method_meta.args[i]["type"]
if _is_supported_default(t):
# strings are special, they need quotes around the value
if t == TYPE_STRING:
dflt_text = str("'", str(method_meta.default_args[dflt_idx]), "'")
# Colors need the parens but things like Vector2 and Rect2 don't
elif t == TYPE_COLOR:
dflt_text = str(
_supported_defaults[t], "(", str(method_meta.default_args[dflt_idx]), ")"
)
elif t == TYPE_OBJECT:
if str(method_meta.default_args[dflt_idx]) == "[Object:null]":
dflt_text = str(_supported_defaults[t], "null")
else:
dflt_text = str(
_supported_defaults[t],
str(method_meta.default_args[dflt_idx]).to_lower()
)
elif t == TYPE_TRANSFORM3D:
#value will be 4 Vector3 and look like: 1, 0, 0, 0, 1, 0, 0, 0, 1 - 0, 0, 0
var sections = str(method_meta.default_args[dflt_idx]).split("-")
var vecs = sections[0].split(",")
vecs.append_array(sections[1].split(","))
var v1 = str("Vector3(", vecs[0], ", ", vecs[1], ", ", vecs[2], ")")
var v2 = str("Vector3(", vecs[3], ", ", vecs[4], ", ", vecs[5], ")")
var v3 = str("Vector3(", vecs[6], ", ", vecs[7], ", ", vecs[8], ")")
var v4 = str("Vector3(", vecs[9], ", ", vecs[10], ", ", vecs[11], ")")
dflt_text = str(
_supported_defaults[t], "(", v1, ", ", v2, ", ", v3, ", ", v4, ")"
)
elif t == TYPE_TRANSFORM2D:
# value will look like: ((1, 0), (0, 1), (0, 0))
var vectors = str(method_meta.default_args[dflt_idx])
vectors = vectors.replace("((", "(")
vectors = vectors.replace("))", ")")
vectors = vectors.replace("(", "Vector2(")
dflt_text = str(_supported_defaults[t], "(", vectors, ")")
elif t == TYPE_RID:
dflt_text = str(_supported_defaults[t], "null")
elif t in [TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_INT32_ARRAY]:
dflt_text = str(_supported_defaults[t], "()")
# Everything else puts the prefix (if one is there) form _supported_defaults
# in front. The to_lower is used b/c for some reason the defaults for
# null, true, false are all "Null", "True", "False".
else:
dflt_text = str(
_supported_defaults[t], str(method_meta.default_args[dflt_idx]).to_lower()
)
else:
_lgr.error(
str(
"Unsupported default param type: ",
method_meta.name,
"-",
method_meta.args[i].name,
" ",
t,
" = ",
method_meta.default_args[dflt_idx]
)
)
dflt_text = str("unsupported=", t)
has_unsupported_defaults = true
# Finally add in the parameter
var dflt_text = _make_stub_default(method_meta.name, i)
to_return.append(CallParameters.new(PARAM_PREFIX + pname, dflt_text))
# Add in extra parameters from stub settings.
if override_size != null:
if(override_size != null):
for i in range(method_meta.args.size(), override_size):
var pname = str(PARAM_PREFIX, "arg", i)
var pname = str(PARAM_PREFIX, 'arg', i)
print('-------- ', i, ' ', pname)
var dflt_text = _make_stub_default(method_meta.name, i)
to_return.append(CallParameters.new(pname, dflt_text))
return [has_unsupported_defaults, to_return]
return [has_unsupported_defaults, to_return];
# Creates a list of parameters with defaults of null unless a default value is
@ -213,37 +145,37 @@ func _make_arg_array(method_meta, override_size):
# If a default is found that we don't know how to handle then this method will
# return null.
func _get_arg_text(arg_array):
var text = ""
var text = ''
for i in range(arg_array.size()):
text += str(arg_array[i].p_name, "=", arg_array[i].default)
if i != arg_array.size() - 1:
text += ", "
text += str(arg_array[i].p_name, '=', arg_array[i].default)
if(i != arg_array.size() -1):
text += ', '
return text
# creates a call to the function in meta in the super's class.
func _get_super_call_text(method_name, args, super_name = ""):
var params = ""
func _get_super_call_text(method_name, args, super_name=""):
var params = ''
for i in range(args.size()):
params += args[i].p_name
if i != args.size() - 1:
params += ", "
if(i != args.size() -1):
params += ', '
return str(super_name, ".", method_name, "(", params, ")")
return str(super_name, 'await super(', params, ')')
func _get_spy_call_parameters_text(args):
var called_with = "null"
var called_with = 'null'
if args.size() > 0:
called_with = "["
if(args.size() > 0):
called_with = '['
for i in range(args.size()):
called_with += args[i].p_name
if i < args.size() - 1:
called_with += ", "
called_with += "]"
if(i < args.size() - 1):
called_with += ', '
called_with += ']'
return called_with
@ -252,48 +184,75 @@ func _get_spy_call_parameters_text(args):
# Public
# ###############
func _get_init_text(meta, args, method_params, param_array):
var text = null
var decleration = str('func ', meta.name, '(', method_params, ')')
var super_params = ''
if(args.size() > 0):
for i in range(args.size()):
super_params += args[i].p_name
if(i != args.size() -1):
super_params += ', '
text = _init_text.format({
"func_decleration":decleration,
"super_params":super_params,
"param_array":param_array,
"method_name":meta.name
})
return text
# Creates a delceration for a function based off of function metadata. All
# types whose defaults are supported will have their values. If a datatype
# is not supported and the parameter has a default, a warning message will be
# printed and the declaration will return null.
func get_function_text(meta, path = null, override_size = null, super_name = ""):
var method_params = ""
#
# path is no longer used
func get_function_text(meta, path=null, override_size=null, super_name=""):
var method_params = ''
var text = null
if(override_size != null):
print('!!!!!! ', override_size)
var result = _make_arg_array(meta, override_size)
var has_unsupported = result[0]
var args = result[1]
var param_array = _get_spy_call_parameters_text(args)
if has_unsupported:
if(has_unsupported):
# This will cause a runtime error. This is the most convenient way to
# to stop running before the error gets more obscure. _make_arg_array
# generates a gut error when unsupported defaults are found.
method_params = null
else:
method_params = _get_arg_text(args)
method_params = _get_arg_text(args);
if param_array == "null":
param_array = "[]"
if(param_array == 'null'):
param_array = '[]'
if method_params != null:
var decleration = str("func ", meta.name, "(", method_params, "):")
text = _func_text.format(
{
"func_decleration": decleration,
"method_name": meta.name,
"param_array": param_array,
"super_call": _get_super_call_text(meta.name, args, super_name)
}
)
if(method_params != null):
if(meta.name == '_init'):
text = _get_init_text(meta, args, method_params, param_array)
else:
var decleration = str('func ', meta.name, '(', method_params, '):')
# decleration = str('# ', meta, "\n", decleration)
text = _func_text.format({
"func_decleration":decleration,
"method_name":meta.name,
"param_array":param_array,
"super_call":_get_super_call_text(meta.name, args, super_name)
})
return text
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger

View file

@ -5,39 +5,34 @@
# ------------------------------------------------------------------------------
var _items = {}
# return the size of _items or the size of an element in _items if "one" was
# specified.
func size(one = null):
func size(one=null):
var to_return = 0
if one == null:
if(one == null):
to_return = _items.size()
elif _items.has(one):
elif(_items.has(one)):
to_return = _items[one].size()
return to_return
# Add an element to "one" if it does not already exist
func add(one, many_item):
if _items.has(one) and !_items[one].has(many_item):
if(_items.has(one) and !_items[one].has(many_item)):
_items[one].append(many_item)
else:
_items[one] = [many_item]
func clear():
_items.clear()
func has(one, many_item):
var to_return = false
if _items.has(one):
if(_items.has(one)):
to_return = _items[one].has(many_item)
return to_return
func to_s():
var to_return = ""
var to_return = ''
for key in _items:
to_return += str(key, ": ", _items[key], "\n")
return to_return

View file

@ -52,7 +52,7 @@ class CmdLineParser:
func _init():
for i in range(OS.get_cmdline_args().size()):
var opt_val = OS.get_cmdline_args()[i].split("=")
var opt_val = OS.get_cmdline_args()[i].split('=')
_opts.append(opt_val)
# Parse out multiple comma delimited values from a command line
@ -60,13 +60,13 @@ class CmdLineParser:
# additional values are comma separated.
func _parse_array_value(full_option):
var value = _parse_option_value(full_option)
var split = value.split(",")
var split = value.split(',')
return split
# Parse out the value of an option. Values are separated from
# the option name with "="
func _parse_option_value(full_option):
if full_option.size() > 1:
if(full_option.size() > 1):
return full_option[1]
else:
return null
@ -77,13 +77,13 @@ class CmdLineParser:
var found = false
var idx = 0
while idx < _opts.size() and !found:
if _opts[idx][0] == name:
while(idx < _opts.size() and !found):
if(_opts[idx][0] == name):
found = true
else:
idx += 1
if found:
if(found):
return idx
else:
return -1
@ -92,7 +92,7 @@ class CmdLineParser:
_used_options.append(option)
var to_return = []
var opt_loc = find_option(option)
if opt_loc != -1:
if(opt_loc != -1):
to_return = _parse_array_value(_opts[opt_loc])
_opts.remove_at(opt_loc)
@ -105,7 +105,7 @@ class CmdLineParser:
_used_options.append(option)
var to_return = null
var opt_loc = find_option(option)
if opt_loc != -1:
if(opt_loc != -1):
to_return = _parse_option_value(_opts[opt_loc])
_opts.remove_at(opt_loc)
@ -127,48 +127,48 @@ class CmdLineParser:
to_return.append(_opts[i][0])
var script_option = to_return.find("-s")
if script_option == -1:
script_option = to_return.find("--script")
if script_option != -1:
to_return.remove_at(script_option + 1)
to_return.remove_at(script_option)
while _used_options.size() > 0:
while(_used_options.size() > 0):
var index = to_return.find(_used_options[0].split("=")[0])
if index != -1:
if(index != -1):
to_return.remove_at(index)
_used_options.remove_at(0)
return to_return
#-------------------------------------------------------------------------------
# Simple class to hold a command line option
#-------------------------------------------------------------------------------
class Option:
var value = null
var option_name = ""
var option_name = ''
var default = null
var description = ""
var description = ''
func _init(name, default_value, desc = ""):
func _init(name,default_value,desc=''):
option_name = name
default = default_value
description = desc
value = null #default_value
value = null#default_value
func pad(to_pad, size, pad_with = " "):
func pad(to_pad, size, pad_with=' '):
var to_return = to_pad
for _i in range(to_pad.length(), size):
to_return += pad_with
return to_return
func to_s(min_space = 0):
func to_s(min_space=0):
var subbed_desc = description
if subbed_desc.find("[default]") != -1:
subbed_desc = subbed_desc.replace("[default]", str(default))
if(subbed_desc.find('[default]') != -1):
subbed_desc = subbed_desc.replace('[default]', str(default))
return pad(option_name, min_space) + subbed_desc
#-------------------------------------------------------------------------------
# The high level interface between this script and the command line options
# supplied. Uses Option class and CmdLineParser to extract information from
@ -176,53 +176,47 @@ class Option:
#-------------------------------------------------------------------------------
var options = []
var _opts = []
var _banner = ""
var _banner = ''
func add(name, default, desc):
options.append(Option.new(name, default, desc))
func get_value(name):
var found = false
var idx = 0
while idx < options.size() and !found:
if options[idx].option_name == name:
while(idx < options.size() and !found):
if(options[idx].option_name == name):
found = true
else:
idx += 1
if found:
if(found):
return options[idx].value
else:
print("COULD NOT FIND OPTION " + name)
return null
func set_banner(banner):
_banner = banner
func print_help():
var longest = 0
for i in range(options.size()):
if options[i].option_name.length() > longest:
if(options[i].option_name.length() > longest):
longest = options[i].option_name.length()
print("---------------------------------------------------------")
print('---------------------------------------------------------')
print(_banner)
print("\nOptions\n-------")
for i in range(options.size()):
print(" " + options[i].to_s(longest + 2))
print("---------------------------------------------------------")
print(' ' + options[i].to_s(longest + 2))
print('---------------------------------------------------------')
func print_options():
for i in range(options.size()):
print(options[i].option_name + "=" + str(options[i].value))
print(options[i].option_name + '=' + str(options[i].value))
func parse():
var parser = CmdLineParser.new()
@ -234,28 +228,24 @@ func parse():
# Without this check, you can't tell the difference between the
# defaults and what was specified, so you can't punch through
# higher level options.
if parser.was_specified(options[i].option_name):
if t == TYPE_INT:
if(parser.was_specified(options[i].option_name)):
if(t == TYPE_INT):
options[i].value = int(parser.get_value(options[i].option_name))
elif t == TYPE_STRING:
elif(t == TYPE_STRING):
options[i].value = parser.get_value(options[i].option_name)
elif t == TYPE_ARRAY:
elif(t == TYPE_ARRAY):
options[i].value = parser.get_array_value(options[i].option_name)
elif t == TYPE_BOOL:
elif(t == TYPE_BOOL):
options[i].value = parser.was_specified(options[i].option_name)
elif t == TYPE_NIL:
print(options[i].option_name + " cannot be processed, it has a nil datatype")
elif(t == TYPE_FLOAT):
options[i].value = parser.get_value(options[i].option_name)
elif(t == TYPE_NIL):
print(options[i].option_name + ' cannot be processed, it has a nil datatype')
else:
print(
(
options[i].option_name
+ " cannot be processed, it has unknown datatype:"
+ str(t)
)
)
print(options[i].option_name + ' cannot be processed, it has unknown datatype:' + str(t))
var unused = parser.get_unused_options()
if unused.size() > 0:
if(unused.size() > 0):
print("Unrecognized options: ", unused)
return false

View file

@ -33,27 +33,23 @@
# ##############################################################################
var _counters = {}
func orphan_count():
return Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT)
func add_counter(name):
_counters[name] = orphan_count()
# Returns the number of orphans created since add_counter was last called for
# the name. Returns -1 to avoid blowing up with an invalid name but still
# be somewhat visible that we've done something wrong.
func get_counter(name):
return orphan_count() - _counters[name] if _counters.has(name) else -1
func print_orphans(name, lgr):
var count = get_counter(name)
if count > 0:
var o = "orphan"
if count > 1:
o = "orphans"
lgr.orphan(str(count, " new ", o, "(", name, ")."))
if(count > 0):
var o = 'orphan'
if(count > 1):
o = 'orphans'
lgr.orphan(str(count, ' new ', o, ' in ', name, '.'))

View file

@ -56,11 +56,11 @@ static func named_parameters(names, values):
var entry = {}
var parray = values[i]
if typeof(parray) != TYPE_ARRAY:
if(typeof(parray) != TYPE_ARRAY):
parray = [values[i]]
for j in range(names.size()):
if j >= parray.size():
if(j >= parray.size()):
entry[names[j]] = null
else:
entry[names[j]] = parray[j]

View file

@ -1,44 +1,37 @@
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _params = null
var _call_count = 0
var _logger = null
func _init(params = null):
func _init(params=null):
_params = params
_logger = _utils.get_logger()
if typeof(_params) != TYPE_ARRAY:
_logger.error("You must pass an array to parameter_handler constructor.")
if(typeof(_params) != TYPE_ARRAY):
_logger.error('You must pass an array to parameter_handler constructor.')
_params = null
func next_parameters():
_call_count += 1
return _params[_call_count - 1]
return _params[_call_count -1]
func get_current_parameters():
return _params[_call_count]
func is_done():
var done = true
if _params != null:
if(_params != null):
done = _call_count == _params.size()
return done
func get_logger():
return _logger
func set_logger(logger):
_logger = logger
func get_call_count():
return _call_count
func get_parameter_count():
return _params.size()

7
addons/gut/plugin.cfg Normal file
View file

@ -0,0 +1,7 @@
[plugin]
name="Gut"
description="Unit Testing tool for Godot."
author="Butch Wesley"
version="7.4.1"
script="gut_plugin.gd"

View file

@ -1,268 +0,0 @@
# ##############################################################################
#(G)odot (U)nit (T)est class
#
# ##############################################################################
# The MIT License (MIT)
# =====================
#
# Copyright (c) 2020 Tom "Butch" Wesley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ##############################################################################
# This is the control that is added via the editor. It exposes GUT settings
# through the editor and delays the creation of the GUT instance until
# Engine.get_main_loop() works as expected.
# ##############################################################################
@tool
extends Control
# ------------------------------------------------------------------------------
# GUT Settings
# ------------------------------------------------------------------------------
@export var _font_name = "AnonymousPro" # (String, "AnonymousPro", "CourierPrime", "LobsterTwo", "Default")
@export var _font_size: int = 20
@export var _font_color: Color = Color(.8, .8, .8, 1)
@export var _background_color: Color = Color(.15, .15, .15, 1)
# Enable/Disable coloring of output.
@export var _color_output: bool = true
# The full/partial name of a script to select upon startup
@export var _select_script: String = ""
# The full/partial name of a test. All tests that contain the string will be
# run
@export var _tests_like: String = ""
# The full/partial name of an Inner Class to be run. All Inner Classes that
# contain the string will be run.
@export var _inner_class_name: String = ""
# Start running tests when the scene finishes loading
@export var _run_on_load = false
# Maximize the GUT control on startup
@export var _should_maximize = false
# Print output to the consol as well
@export var _should_print_to_console = true
# Display orphan counts at the end of tests/scripts.
@export var _show_orphans = true
# The log level.
@export var _log_level = 1 # (int, "Fail/Errors", "Errors/Warnings/Test Names", "Everything")
# When enabled GUT will yield between tests to give the GUI time to paint.
# Disabling this can make the program appear to hang and can have some
# unwanted consequences with the timing of freeing objects
@export var _yield_between_tests = true
# When GUT compares values it first checks the types to prevent runtime errors.
# This behavior can be disabled if desired. This flag was added early in
# development to prevent any breaking changes and will likely be removed in
# the future.
@export var _disable_strict_datatype_checks = false
# The prefix used to find test methods.
@export var _test_prefix = "test_"
# The prefix used to find test scripts.
@export var _file_prefix = "test_"
# The file extension for test scripts (I don't think you can change this and
# everythign work).
@export var _file_extension = ".gd"
# The prefix used to find Inner Test Classes.
@export var _inner_class_prefix = "Test"
# The directory GUT will use to write any temporary files. This isn't used
# much anymore since there was a change to the double creation implementation.
# This will be removed in a later release.
@export var _temp_directory: String = "user://gut_temp_directory"
# The path and filename for exported test information.
@export var _export_path: String = ""
# When enabled, any directory added will also include its subdirectories when
# GUT looks for test scripts.
@export var _include_subdirectories = false
# Allow user to add test directories via editor. This is done with strings
# instead of an array because the interface for editing arrays is really
# cumbersome and complicates testing because arrays set through the editor
# apply to ALL instances. This also allows the user to use the built in
# dialog to pick a directory.
@export var _directory1 = "" # (String, DIR)
@export var _directory2 = "" # (String, DIR)
@export var _directory3 = "" # (String, DIR)
@export var _directory4 = "" # (String, DIR)
@export var _directory5 = "" # (String, DIR)
@export var _directory6 = "" # (String, DIR)
# Must match the types in _utils for double strategy
@export var _double_strategy = 1 # (int, "FULL", "PARTIAL")
# Path3D and filename to the script to run before all tests are run.
@export var _pre_run_script = "" # (String, FILE)
# Path3D and filename to the script to run after all tests are run.
@export var _post_run_script = "" # (String, FILE)
# Path3D to the file that gut will export results to in the junit xml format
@export var _junit_xml_file = "" # (String, FILE)
# Flag to include a timestamp in the filename of _junit_xml_file
@export var _junit_xml_timestamp: bool = false
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Signals
# ------------------------------------------------------------------------------
# Emitted when all the tests have finished running.
signal tests_finished
# Emitted when GUT is ready to be interacted with, and before any tests are run.
signal gut_ready
# ------------------------------------------------------------------------------
# Private stuff.
# ------------------------------------------------------------------------------
var _gut = null
var _lgr = null
var _cancel_import = false
var _placeholder = null
func _init():
# This min size has to be what the min size of the GutScene's min size is
# but it has to be set here and not inferred i think.
minimum_size = Vector2(740, 250)
func _ready():
# Must call this deferred so that there is enough time for
# Engine.get_main_loop() is populated and the psuedo singleton utils.gd
# can be setup correctly.
if Engine.editor_hint:
_placeholder = load("res://addons/gut/GutScene.tscn").instantiate()
call_deferred("add_child", _placeholder)
_placeholder.size = size
else:
call_deferred("_setup_gut")
connect("resized",Callable(self,"_on_resized"))
func _on_resized():
if _placeholder != null:
_placeholder.size = size
# Templates can be missing if tests are exported and the export config for the
# project does not include '*.txt' files. This check and related flags make
# sure GUT does not blow up and that the error is not lost in all the import
# output that is generated as well as ensuring that no tests are run.
#
# Assumption: This is only a concern when running from the scene since you
# cannot run GUT from the command line in an exported game.
func _check_for_templates():
var f = File.new()
if !f.file_exists("res://addons/gut/double_templates/function_template.txt"):
_lgr.error(
'Templates are missing. Make sure you are exporting "*.txt" or "addons/gut/double_templates/*.txt".'
)
_run_on_load = false
_cancel_import = true
return false
return true
func _setup_gut():
var _utils = load("res://addons/gut/utils.gd").get_instance()
_lgr = _utils.get_logger()
_gut = load("res://addons/gut/gut.gd").new()
_gut.connect("tests_finished",Callable(self,"_on_tests_finished"))
if !_check_for_templates():
return
_gut._select_script = _select_script
_gut._tests_like = _tests_like
_gut._inner_class_name = _inner_class_name
_gut._test_prefix = _test_prefix
_gut._file_prefix = _file_prefix
_gut._file_extension = _file_extension
_gut._inner_class_prefix = _inner_class_prefix
_gut._temp_directory = _temp_directory
_gut.set_should_maximize(_should_maximize)
_gut.set_yield_between_tests(_yield_between_tests)
_gut.disable_strict_datatype_checks(_disable_strict_datatype_checks)
_gut.set_export_path(_export_path)
_gut.set_include_subdirectories(_include_subdirectories)
_gut.set_double_strategy(_double_strategy)
_gut.set_pre_run_script(_pre_run_script)
_gut.set_post_run_script(_post_run_script)
_gut.set_color_output(_color_output)
_gut.show_orphans(_show_orphans)
_gut.set_junit_xml_file(_junit_xml_file)
_gut.set_junit_xml_timestamp(_junit_xml_timestamp)
get_parent().add_child(_gut)
if !_utils.is_version_ok():
return
_gut.set_log_level(_log_level)
_gut.add_directory(_directory1)
_gut.add_directory(_directory2)
_gut.add_directory(_directory3)
_gut.add_directory(_directory4)
_gut.add_directory(_directory5)
_gut.add_directory(_directory6)
_gut.get_logger().disable_printer("console", !_should_print_to_console)
# When file logging enabled then the log will contain terminal escape
# strings. So when running the scene this is disabled. Also if enabled
# this may cause duplicate entries into the logs.
_gut.get_logger().disable_printer("terminal", true)
_gut.get_gui().set_font_size(_font_size)
_gut.get_gui().set_font(_font_name)
_gut.get_gui().set_default_font_color(_font_color)
_gut.get_gui().set_background_color(_background_color)
_gut.get_gui().size = size
emit_signal("gut_ready")
if _run_on_load:
# Run the test scripts. If one has been selected then only run that one
# otherwise all tests will be run.
var run_rest_of_scripts = _select_script == null
_gut.test_scripts(run_rest_of_scripts)
func _is_ready_to_go(action):
if _gut == null:
push_error(
str(
"GUT is not ready for ",
action,
" yet. Perform actions on GUT in/after the gut_ready signal."
)
)
return _gut != null
func _on_tests_finished():
emit_signal("tests_finished")
func get_gut():
return _gut
func export_if_tests_found():
if _is_ready_to_go("export_if_tests_found"):
_gut.export_if_tests_found()
func import_tests_if_none_found():
if _is_ready_to_go("import_tests_if_none_found") and !_cancel_import:
_gut.import_tests_if_none_found()

View file

@ -4,8 +4,8 @@
class Printer:
var _format_enabled = true
var _disabled = false
var _printer_name = "NOT SET"
var _show_name = false # used for debugging, set manually
var _printer_name = 'NOT SET'
var _show_name = false # used for debugging, set manually
func get_format_enabled():
return _format_enabled
@ -13,16 +13,16 @@ class Printer:
func set_format_enabled(format_enabled):
_format_enabled = format_enabled
func send(text, fmt = null):
if _disabled:
func send(text, fmt=null):
if(_disabled):
return
var formatted = text
if fmt != null and _format_enabled:
if(fmt != null and _format_enabled):
formatted = format_text(text, fmt)
if _show_name:
formatted = str("(", _printer_name, ")") + formatted
if(_show_name):
formatted = str('(', _printer_name, ')') + formatted
_output(formatted)
@ -41,58 +41,85 @@ class Printer:
func format_text(text, fmt):
return text
# ------------------------------------------------------------------------------
# Responsible for sending text to a GUT gui.
# ------------------------------------------------------------------------------
class GutGuiPrinter:
extends Printer
var _gut = null
var _textbox = null
var _colors = {red = Color.RED, yellow = Color.YELLOW, green = Color.GREEN}
var _colors = {
red = Color.RED,
yellow = Color.YELLOW,
green = Color.GREEN
}
func _init():
_printer_name = "gui"
_printer_name = 'gui'
func _wrap_with_tag(text, tag):
return str("[", tag, "]", text, "[/", tag, "]")
return str('[', tag, ']', text, '[/', tag, ']')
func _color_text(text, c_word):
return "[color=" + c_word + "]" + text + "[/color]"
return '[color=' + c_word + ']' + text + '[/color]'
# Remember, we have to use push and pop because the output from the tests
# can contain [] in it which can mess up the formatting. There is no way
# as of 3.4 that you can get the bbcode out of RTL when using push and pop.
#
# The only way we could get around this is by adding in non-printable
# whitespace after each "[" that is in the text. Then we could maybe do
# this another way and still be able to get the bbcode out, or generate it
# at the same time in a buffer (like we tried that one time).
#
# Since RTL doesn't have good search and selection methods, and those are
# really handy in the editor, it isn't worth making bbcode that can be used
# there as well.
#
# You'll try to get it so the colors can be the same in the editor as they
# are in the output. Good luck, and I hope I typed enough to not go too
# far that rabbit hole before finding out it's not worth it.
func format_text(text, fmt):
var box = _gut.get_gui().get_text_box()
if(_textbox == null):
return
if fmt == "bold":
box.push_bold()
elif fmt == "underline":
box.push_underline()
elif _colors.has(fmt):
box.push_color(_colors[fmt])
if(fmt == 'bold'):
_textbox.push_bold()
elif(fmt == 'underline'):
_textbox.push_underline()
elif(_colors.has(fmt)):
_textbox.push_color(_colors[fmt])
else:
# just pushing something to pop.
box.push_normal()
_textbox.push_normal()
box.add_text(text)
box.pop()
_textbox.add_text(text)
_textbox.pop()
return ""
return ''
func _output(text):
_gut.get_gui().get_text_box().add_text(text)
if(_textbox == null):
return
func get_gut():
return _gut
_textbox.add_text(text)
func set_gut(gut):
_gut = gut
func get_textbox():
return _textbox
func set_textbox(textbox):
_textbox = textbox
# This can be very very slow when the box has a lot of text.
func clear_line():
var box = _gut.get_gui().get_text_box()
box.remove_line(box.get_line_count() - 1)
box.update()
_textbox.remove_line(_textbox.get_line_count() - 1)
_textbox.queue_redraw()
func get_bbcode():
return _textbox.text
func get_disabled():
return _disabled and _textbox != null
# ------------------------------------------------------------------------------
# This AND TerminalPrinter should not be enabled at the same time since it will
@ -101,21 +128,20 @@ class GutGuiPrinter:
# ------------------------------------------------------------------------------
class ConsolePrinter:
extends Printer
var _buffer = ""
var _buffer = ''
func _init():
_printer_name = "console"
_printer_name = 'console'
# suppresses output until it encounters a newline to keep things
# inline as much as possible.
func _output(text):
if text.ends_with("\n"):
print(_buffer + text.left(text.length() - 1))
_buffer = ""
if(text.ends_with("\n")):
print(_buffer + text.left(text.length() -1))
_buffer = ''
else:
_buffer += text
# ------------------------------------------------------------------------------
# Prints text to terminal, formats some words.
# ------------------------------------------------------------------------------
@ -123,18 +149,21 @@ class TerminalPrinter:
extends Printer
var escape = PackedByteArray([0x1b]).get_string_from_ascii()
var cmd_colors = {
red = escape + "[31m",
yellow = escape + "[33m",
green = escape + "[32m",
underline = escape + "[4m",
bold = escape + "[1m",
default = escape + "[0m",
clear_line = escape + "[2K"
var cmd_colors = {
red = escape + '[31m',
yellow = escape + '[33m',
green = escape + '[32m',
underline = escape + '[4m',
bold = escape + '[1m',
default = escape + '[0m',
clear_line = escape + '[2K'
}
func _init():
_printer_name = "terminal"
_printer_name = 'terminal'
func _output(text):
# Note, printraw does not print to the console.
@ -147,7 +176,7 @@ class TerminalPrinter:
send(cmd_colors.clear_line)
func back(n):
send(escape + str("[", n, "D"))
send(escape + str('[', n, 'D'))
func forward(n):
send(escape + str("[", n, "C"))
send(escape + str('[', n, 'C'))

View file

@ -4,61 +4,56 @@
# of a run and exporting it in a specific format. This can also serve as a
# unofficial GUT export format.
# ------------------------------------------------------------------------------
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var json = JSON.new()
func _export_tests(summary_script):
var to_return = {}
var tests = summary_script.get_tests()
for key in tests.keys():
to_return[key] = {
"status": tests[key].get_status(),
"passing": tests[key].pass_texts,
"failing": tests[key].fail_texts,
"pending": tests[key].pending_texts,
"orphans": tests[key].orphans,
"status":tests[key].get_status(),
"passing":tests[key].pass_texts,
"failing":tests[key].fail_texts,
"pending":tests[key].pending_texts,
"orphans":tests[key].orphans
}
return to_return
# TODO
# errors
func _export_scripts(summary):
if summary == null:
if(summary == null):
return {}
var scripts = {}
for s in summary.get_scripts():
scripts[s.name] = {
"props":
{
"tests": s._tests.size(),
"pending": s.get_pending_count(),
"failures": s.get_fail_count(),
'props':{
"tests":s._tests.size(),
"pending":s.get_pending_count(),
"failures":s.get_fail_count(),
},
"tests": _export_tests(s)
"tests":_export_tests(s)
}
return scripts
func _make_results_dict():
var result = {
"test_scripts":
{
"props":
{
"pending": 0,
"failures": 0,
"passing": 0,
"tests": 0,
"time": 0,
"orphans": 0,
"errors": 0,
"warnings": 0
var result = {
'test_scripts':{
"props":{
"pending":0,
"failures":0,
"passing":0,
"tests":0,
"time":0,
"orphans":0,
"errors":0,
"warnings":0
},
"scripts": []
"scripts":[]
}
}
return result
@ -67,15 +62,15 @@ func _make_results_dict():
# TODO
# time
# errors
func get_results_dictionary(gut, include_scripts = true):
func get_results_dictionary(gut, include_scripts=true):
var summary = gut.get_summary()
var scripts = []
if include_scripts:
if(include_scripts):
scripts = _export_scripts(summary)
var result = _make_results_dict()
if summary != null:
var result = _make_results_dict()
if(summary != null):
var totals = summary.get_totals()
var props = result.test_scripts.props
@ -83,10 +78,10 @@ func get_results_dictionary(gut, include_scripts = true):
props.failures = totals.failing
props.passing = totals.passing_tests
props.tests = totals.tests
props.errors = gut.get_logger().get_errors().size()
props.warnings = gut.get_logger().get_warnings().size()
props.time = gut.get_gui().elapsed_time_as_str().replace("s", "")
props.orphans = gut.get_orphan_counter().get_counter("total")
props.errors = gut.logger.get_errors().size()
props.warnings = gut.logger.get_warnings().size()
props.time = gut.get_elapsed_time()
props.orphans = gut.get_orphan_counter().get_counter('total')
result.test_scripts.scripts = scripts
return result
@ -94,22 +89,23 @@ func get_results_dictionary(gut, include_scripts = true):
func write_json_file(gut, path):
var dict = get_results_dictionary(gut)
var json = JSON.stringify(dict, " ")
var json_text = json.stringify(dict, ' ')
var f_result = _utils.write_file(path, json)
if f_result != OK:
var f_result = _utils.write_file(path, json_text)
if(f_result != OK):
var msg = str("Error: ", f_result, ". Could not create export file ", path)
_utils.get_logger().error(msg)
return f_result
func write_summary_file(gut, path):
var dict = get_results_dictionary(gut, false)
var json = JSON.stringify(dict, " ")
var json_text = json.stringify(dict, ' ')
var f_result = _utils.write_file(path, json)
if f_result != OK:
var f_result = _utils.write_file(path, json_text)
if(f_result != OK):
var msg = str("Error: ", f_result, ". Could not create export file ", path)
_utils.get_logger().error(msg)

346
addons/gut/script_parser.gd Normal file
View file

@ -0,0 +1,346 @@
# ------------------------------------------------------------------------------
# List of methods that should not be overloaded when they are not defined
# in the class being doubled. These either break things if they are
# overloaded or do not have a "super" equivalent so we can't just pass
# through.
const BLACKLIST = [
'_draw',
'_enter_tree',
'_exit_tree',
'_get_minimum_size', # Nonexistent function _get_minimum_size
'_get', # probably
'_input',
'_notification',
'_physics_process',
'_process',
'_set',
'_to_string', # nonexistant function super._to_string
'_unhandled_input',
'_unhandled_key_input',
'draw_mesh', # issue with one parameter, value is `Null((..), (..), (..))``
'emit_signal', # can't handle extra parameters to be sent with signal.
'get_path',
'get_script',
'get',
'has_method',
'print_orphan_nodes'
]
# ------------------------------------------------------------------------------
# Combins the meta for the method with additional information.
# * flag for whether the method is local
# * adds a 'default' property to all parameters that can be easily checked per
# parameter
# ------------------------------------------------------------------------------
class ParsedMethod:
var _meta = {}
var meta = _meta :
get: return _meta
set(val): return;
var _parameters = []
var is_local = false
const NO_DEFAULT = '__no__default__'
func _init(metadata):
_meta = metadata
var start_default = _meta.args.size() - _meta.default_args.size()
for i in range(_meta.args.size()):
var arg = _meta.args[i]
# Add a "default" property to the metadata so we don't have to do
# weird default position math again.
if(i >= start_default):
arg['default'] = _meta.default_args[start_default - i]
else:
arg['default'] = NO_DEFAULT
_parameters.append(arg)
func is_black_listed():
return BLACKLIST.find(_meta.name) != -1
func to_s():
var s = _meta.name + "("
for i in range(_meta.args.size()):
var arg = _meta.args[i]
if(str(arg.default) != NO_DEFAULT):
var val = str(arg.default)
if(val == ''):
val = '""'
s += str(arg.name, ' = ', val)
else:
s += str(arg.name)
if(i != _meta.args.size() -1):
s += ', '
s += ")"
return s
# ------------------------------------------------------------------------------
# Doesn't know if a method is local and in super, but not sure if that will
# ever matter.
# ------------------------------------------------------------------------------
class ParsedScript:
# All methods indexed by name.
var _methods_by_name = {}
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _script_path = null
var script_path = _script_path :
get: return _script_path
set(val): return;
var _subpath = null
var subpath = null :
get: return _subpath
set(val): return;
var _resource = null
var resource = null :
get: return _resource
set(val): return;
var _native_instance = null
var is_native = false :
get: return _native_instance != null
set(val): return;
func unreference():
if(_native_instance != null):
_native_instance.free()
return super()
func _init(script_or_inst, inner_class=null):
var to_load = script_or_inst
if(_utils.is_native_class(to_load)):
_resource = to_load
_native_instance = to_load.new()
else:
if(!script_or_inst is Resource):
to_load = load(script_or_inst.get_script().get_path())
_script_path = to_load.resource_path
if(inner_class != null):
_subpath = _find_subpath(to_load, inner_class)
if(inner_class == null):
_resource = to_load
else:
_resource = inner_class
to_load = inner_class
_parse_methods(to_load)
func _has_flag_to_be_ignored(flags):
return false
# I think this is getting anything that has the 1 flag set...I think
return flags & (1 << 2) == 0 && \
flags & (1 << 4) == 0 && \
flags & (1 << 6) == 0
func _print_flags(meta):
print(str(meta.name, ':').rpad(30), str(meta.flags).rpad(4), ' = ', _utils.dec2bistr(meta.flags, 10))
func _get_native_methods(base_type):
var to_return = []
if(base_type != null):
var source = str('extends ', base_type)
var inst = _utils.create_script_from_source(source).new()
to_return = inst.get_method_list()
if(! inst is RefCounted):
inst.free()
return to_return
func _parse_methods(thing):
var methods = []
if(is_native):
methods = _native_instance.get_method_list()
else:
var base_type = thing.get_instance_base_type()
methods = _get_native_methods(base_type)
for m in methods:
if(!_has_flag_to_be_ignored(m.flags)):
var parsed = ParsedMethod.new(m)
_methods_by_name[m.name] = parsed
# _init must always be included so that we can initialize
# double_tools
if(m.name == '_init'):
parsed.is_local = true
# This loop will overwrite all entries in _methods_by_name with the local
# method object so there is only ever one listing for a function with
# the right "is_local" flag.
if(!is_native):
methods = thing.get_script_method_list()
for m in methods:
var parsed_method = ParsedMethod.new(m)
parsed_method.is_local = true
_methods_by_name[m.name] = parsed_method
func _find_subpath(parent_script, inner):
var const_map = parent_script.get_script_constant_map()
var consts = const_map.keys()
var const_idx = 0
var found = false
var to_return = null
while(const_idx < consts.size() and !found):
var key = consts[const_idx]
var const_val = const_map[key]
if(typeof(const_val) == TYPE_OBJECT):
if(const_val == inner):
found = true
to_return = key
else:
to_return = _find_subpath(const_val, inner)
if(to_return != null):
to_return = str(key, '.', to_return)
found = true
const_idx += 1
return to_return
func get_method(name):
return _methods_by_name[name]
func is_method_blacklisted(m_name):
if(_methods_by_name.has(m_name)):
return _methods_by_name[m_name].is_black_listed()
func get_super_method(name):
var to_return = get_method(name)
if(to_return.is_local):
to_return = null
return to_return
func get_local_method(name):
var to_return = get_method(name)
if(!to_return.is_local):
to_return = null
return to_return
func get_sorted_method_names():
var keys = _methods_by_name.keys()
keys.sort()
return keys
func get_local_method_names():
var names = []
for method in _methods_by_name:
if(_methods_by_name[method].is_local):
names.append(method)
return names
func get_super_method_names():
var names = []
for method in _methods_by_name:
if(!_methods_by_name[method].is_local):
names.append(method)
return names
func get_local_methods():
var to_return = []
for key in _methods_by_name:
var method = _methods_by_name[key]
if(method.is_local):
to_return.append(method)
return to_return
func get_super_methods():
var to_return = []
for key in _methods_by_name:
var method = _methods_by_name[key]
if(!method.is_local):
to_return.append(method)
return to_return
func get_extends_text():
var text = null
if(is_native):
text = str("extends ", _native_instance.get_class())
else:
text = str("extends '", _script_path, "'")
if(_subpath != null):
text += '.' + _subpath
return text
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
var scripts = {}
var _utils = load('res://addons/gut/utils.gd').get_instance()
func _get_instance_id(thing):
var inst_id = null
if(_utils.is_native_class(thing)):
var id_str = str(thing).replace("<", '').replace(">", '').split('#')[1]
inst_id = id_str.to_int()
elif(typeof(thing) == TYPE_STRING):
if(FileAccess.file_exists(thing)):
inst_id = load(thing).get_instance_id()
else:
inst_id = thing.get_instance_id()
return inst_id
func parse(thing, inner_thing=null):
var key = -1
if(inner_thing == null):
key = _get_instance_id(thing)
else:
key = _get_instance_id(inner_thing)
var parsed = null
if(key != null):
if(scripts.has(key)):
parsed = scripts[key]
else:
var obj = instance_from_id(_get_instance_id(thing))
var inner = null
if(inner_thing != null):
inner = instance_from_id(_get_instance_id(inner_thing))
if(obj is Resource or _utils.is_native_class(obj)):
parsed = ParsedScript.new(obj, inner)
scripts[key] = parsed
return parsed

View file

@ -25,8 +25,8 @@
# ##############################################################################
# Some arbitrary string that should never show up by accident. If it does, then
# shame on you.
const ARG_NOT_SET = "_*_argument_*_is_*_not_set_*_"
# shame on you.
const ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_'
# This hash holds the objects that are being watched, the signals that are being
# watched, and an array of arrays that contains arguments that were passed
@ -47,25 +47,24 @@ const ARG_NOT_SET = "_*_argument_*_is_*_not_set_*_"
#
# In this sample:
# - signal1 on the ref1 object was emitted 3 times and each time, zero
# parameters were passed.
# parameters were passed.
# - signal3 on ref1 was emitted once and passed a single parameter
# - some_signal on ref2 was never emitted.
# - other_signal on ref2 was emitted 3 times, each time with 3 parameters.
var _watched_signals = {}
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _lgr = _utils.get_logger()
func _add_watched_signal(obj, name):
# SHORTCIRCUIT - ignore dupes
if _watched_signals.has(obj) and _watched_signals[obj].has(name):
if(_watched_signals.has(obj) and _watched_signals[obj].has(name)):
return
if !_watched_signals.has(obj):
_watched_signals[obj] = {name: []}
if(!_watched_signals.has(obj)):
_watched_signals[obj] = {name:[]}
else:
_watched_signals[obj][name] = []
obj.connect(name,Callable(self,"_on_watched_signal").bind(obj, name))
obj.connect(name,Callable(self,'_on_watched_signal').bind(obj,name))
# This handles all the signals that are watched. It supports up to 9 parameters
# which could be emitted by the signal and the two parameters used when it is
@ -76,113 +75,113 @@ func _add_watched_signal(obj, name):
# Based on the documentation of emit_signal, it appears you can only pass up
# to 4 parameters when firing a signal. I haven't verified this, but this should
# future proof this some if the value ever grows.
func _on_watched_signal(
arg1 = ARG_NOT_SET,
arg2 = ARG_NOT_SET,
arg3 = ARG_NOT_SET,
arg4 = ARG_NOT_SET,
arg5 = ARG_NOT_SET,
arg6 = ARG_NOT_SET,
arg7 = ARG_NOT_SET,
arg8 = ARG_NOT_SET,
arg9 = ARG_NOT_SET,
arg10 = ARG_NOT_SET,
arg11 = ARG_NOT_SET
):
func _on_watched_signal(arg1=ARG_NOT_SET, arg2=ARG_NOT_SET, arg3=ARG_NOT_SET, \
arg4=ARG_NOT_SET, arg5=ARG_NOT_SET, arg6=ARG_NOT_SET, \
arg7=ARG_NOT_SET, arg8=ARG_NOT_SET, arg9=ARG_NOT_SET, \
arg10=ARG_NOT_SET, arg11=ARG_NOT_SET):
var args = [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11]
# strip off any unused vars.
var idx = args.size() - 1
while str(args[idx]) == ARG_NOT_SET:
var idx = args.size() -1
while(str(args[idx]) == ARG_NOT_SET):
args.remove_at(idx)
idx -= 1
# retrieve object and signal name from the array and remove_at them. These
# will always be at the end since they are added when the connect happens.
var signal_name = args[args.size() - 1]
var signal_name = args[args.size() -1]
args.pop_back()
var object = args[args.size() - 1]
var object = args[args.size() -1]
args.pop_back()
_watched_signals[object][signal_name].append(args)
if(_watched_signals.has(object)):
_watched_signals[object][signal_name].append(args)
else:
_lgr.error(str("signal_watcher._on_watched_signal: Got signal for unwatched object: ", object, '::', signal_name))
# This parameter stuff should go into test.gd not here. This thing works
# just fine the way it is.
func _obj_name_pair(obj_or_signal, signal_name=null):
var to_return = {
'object' : obj_or_signal,
'signal_name' : signal_name
}
if(obj_or_signal is Signal):
to_return.object = obj_or_signal.get_object()
to_return.signal_name = obj_or_signal.get_name()
return to_return
func does_object_have_signal(object, signal_name):
var signals = object.get_signal_list()
for i in range(signals.size()):
if signals[i]["name"] == signal_name:
if(signals[i]['name'] == signal_name):
return true
return false
func watch_signals(object):
var signals = object.get_signal_list()
for i in range(signals.size()):
_add_watched_signal(object, signals[i]["name"])
_add_watched_signal(object, signals[i]['name'])
func watch_signal(object, signal_name):
var did = false
if does_object_have_signal(object, signal_name):
if(does_object_have_signal(object, signal_name)):
_add_watched_signal(object, signal_name)
did = true
else:
_utils.get_logger().warn(str(object, ' does not have signal ', signal_name))
return did
func get_emit_count(object, signal_name):
var to_return = -1
if is_watching(object, signal_name):
if(is_watching(object, signal_name)):
to_return = _watched_signals[object][signal_name].size()
return to_return
func did_emit(object, signal_name):
func did_emit(object, signal_name=null):
var vals = _obj_name_pair(object, signal_name)
var did = false
if is_watching(object, signal_name):
did = get_emit_count(object, signal_name) != 0
if(is_watching(vals.object, vals.signal_name)):
did = get_emit_count(vals.object, vals.signal_name) != 0
return did
func print_object_signals(object):
var list = object.get_signal_list()
for i in range(list.size()):
print(list[i].name, "\n ", list[i])
func get_signal_parameters(object, signal_name, index = -1):
func get_signal_parameters(object, signal_name, index=-1):
var params = null
if is_watching(object, signal_name):
if(is_watching(object, signal_name)):
var all_params = _watched_signals[object][signal_name]
if all_params.size() > 0:
if index == -1:
index = all_params.size() - 1
if(all_params.size() > 0):
if(index == -1):
index = all_params.size() -1
params = all_params[index]
return params
func is_watching_object(object):
return _watched_signals.has(object)
func is_watching(object, signal_name):
return _watched_signals.has(object) and _watched_signals[object].has(signal_name)
func clear():
for obj in _watched_signals:
if _utils.is_not_freed(obj):
if(_utils.is_not_freed(obj)):
for signal_name in _watched_signals[obj]:
obj.disconnect(signal_name,Callable(self,"_on_watched_signal"))
obj.disconnect(signal_name, Callable(self,'_on_watched_signal'))
_watched_signals.clear()
# Returns a list of all the signal names that were emitted by the object.
# If the object is not being watched then an empty list is returned.
func get_signals_emitted(obj):
var emitted = []
if is_watching_object(obj):
if(is_watching_object(obj)):
for signal_name in _watched_signals[obj]:
if _watched_signals[obj][signal_name].size() > 0:
if(_watched_signals[obj][signal_name].size() > 0):
emitted.append(signal_name)
return emitted

View file

@ -2,7 +2,7 @@
importer="font_data_bmfont"
type="FontFile"
uid="uid://glgbaf0v7pq4"
uid="uid://cfawt18qr4256"
path="res://.godot/imported/source_code_pro.fnt-042fb383b3c7b4c19e67c852f7fbefca.fontdata"
[deps]

View file

@ -9,119 +9,100 @@
# },
# }
var _calls = {}
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _lgr = _utils.get_logger()
var _compare = _utils.Comparator.new()
func _find_parameters(call_params, params_to_find):
var found = false
var idx = 0
while idx < call_params.size() and !found:
while(idx < call_params.size() and !found):
var result = _compare.deep(call_params[idx], params_to_find)
if result.are_equal:
if(result.are_equal):
found = true
else:
idx += 1
return found
func _get_params_as_string(params):
var to_return = ""
if params == null:
return ""
var to_return = ''
if(params == null):
return ''
for i in range(params.size()):
if params[i] == null:
to_return += "null"
if(params[i] == null):
to_return += 'null'
else:
if typeof(params[i]) == TYPE_STRING:
if(typeof(params[i]) == TYPE_STRING):
to_return += str('"', params[i], '"')
else:
to_return += str(params[i])
if i != params.size() - 1:
to_return += ", "
if(i != params.size() -1):
to_return += ', '
return to_return
func add_call(variant, method_name, parameters = null):
if !_calls.has(variant):
func add_call(variant, method_name, parameters=null):
if(!_calls.has(variant)):
_calls[variant] = {}
if !_calls[variant].has(method_name):
if(!_calls[variant].has(method_name)):
_calls[variant][method_name] = []
_calls[variant][method_name].append(parameters)
func was_called(variant, method_name, parameters = null):
func was_called(variant, method_name, parameters=null):
var to_return = false
if _calls.has(variant) and _calls[variant].has(method_name):
if parameters:
if(_calls.has(variant) and _calls[variant].has(method_name)):
if(parameters):
to_return = _find_parameters(_calls[variant][method_name], parameters)
else:
to_return = true
return to_return
func get_call_parameters(variant, method_name, index = -1):
func get_call_parameters(variant, method_name, index=-1):
var to_return = null
var get_index = -1
if _calls.has(variant) and _calls[variant].has(method_name):
if(_calls.has(variant) and _calls[variant].has(method_name)):
var call_size = _calls[variant][method_name].size()
if index == -1:
if(index == -1):
# get the most recent call by default
get_index = call_size - 1
get_index = call_size -1
else:
get_index = index
if get_index < call_size:
if(get_index < call_size):
to_return = _calls[variant][method_name][get_index]
else:
_lgr.error(
str(
"Specified index ",
index,
" is outside range of the number of registered calls: ",
call_size
)
)
_lgr.error(str('Specified index ', index, ' is outside range of the number of registered calls: ', call_size))
return to_return
func call_count(instance, method_name, parameters = null):
func call_count(instance, method_name, parameters=null):
var to_return = 0
if was_called(instance, method_name):
if parameters:
if(was_called(instance, method_name)):
if(parameters):
for i in range(_calls[instance][method_name].size()):
if _calls[instance][method_name][i] == parameters:
if(_calls[instance][method_name][i] == parameters):
to_return += 1
else:
to_return = _calls[instance][method_name].size()
return to_return
func clear():
_calls = {}
func get_call_list_as_string(instance):
var to_return = ""
if _calls.has(instance):
var to_return = ''
if(_calls.has(instance)):
for method in _calls[instance]:
for i in range(_calls[instance][method].size()):
to_return += str(
method, "(", _get_params_as_string(_calls[instance][method][i]), ")\n"
)
to_return += str(method, '(', _get_params_as_string(_calls[instance][method][i]), ")\n")
return to_return
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger

View file

@ -1,61 +1,57 @@
var _utils = load("res://addons/gut/utils.gd").get_instance()
class_name GutStringUtils
var _utils = load('res://addons/gut/utils.gd').get_instance()
# Hash containing all the built in types in Godot. This provides an English
# name for the types that corosponds with the type constants defined in the
# engine.
var types = {}
var NativeScriptClass = null
func _init_types_dictionary():
types[TYPE_NIL] = "TYPE_NIL"
types[TYPE_BOOL] = "Bool"
types[TYPE_INT] = "Int"
types[TYPE_FLOAT] = "Float/Real"
types[TYPE_STRING] = "String"
types[TYPE_VECTOR2] = "Vector2"
types[TYPE_RECT2] = "Rect2"
types[TYPE_VECTOR3] = "Vector3"
types[TYPE_NIL] = 'TYPE_NIL'
types[TYPE_BOOL] = 'Bool'
types[TYPE_INT] = 'Int'
types[TYPE_FLOAT] = 'Float/Real'
types[TYPE_STRING] = 'String'
types[TYPE_VECTOR2] = 'Vector2'
types[TYPE_RECT2] = 'Rect2'
types[TYPE_VECTOR3] = 'Vector3'
#types[8] = 'Matrix32'
types[TYPE_PLANE] = "Plane"
types[TYPE_QUATERNION] = "QUAT"
types[TYPE_AABB] = "AABB"
types[TYPE_PLANE] = 'Plane'
types[TYPE_QUATERNION] = 'QUAT'
types[TYPE_AABB] = 'AABB'
#types[12] = 'Matrix3'
types[TYPE_TRANSFORM3D] = "Transform3D"
types[TYPE_COLOR] = "Color"
types[TYPE_TRANSFORM3D] = 'Transform3D'
types[TYPE_COLOR] = 'Color'
#types[15] = 'Image'
types[TYPE_NODE_PATH] = "Node Path3D"
types[TYPE_RID] = "RID"
types[TYPE_OBJECT] = "TYPE_OBJECT"
types[TYPE_NODE_PATH] = 'Node Path3D'
types[TYPE_RID] = 'RID'
types[TYPE_OBJECT] = 'TYPE_OBJECT'
#types[19] = 'TYPE_INPUT_EVENT'
types[TYPE_DICTIONARY] = "Dictionary"
types[TYPE_ARRAY] = "Array"
types[TYPE_PACKED_BYTE_ARRAY] = "TYPE_PACKED_BYTE_ARRAY"
types[TYPE_PACKED_INT32_ARRAY] = "TYPE_PACKED_INT32_ARRAY"
types[TYPE_PACKED_FLOAT32_ARRAY] = "TYPE_PACKED_FLOAT32_ARRAY"
types[TYPE_PACKED_STRING_ARRAY] = "TYPE_PACKED_STRING_ARRAY"
types[TYPE_PACKED_VECTOR2_ARRAY] = "TYPE_PACKED_VECTOR2_ARRAY"
types[TYPE_PACKED_VECTOR3_ARRAY] = "TYPE_PACKED_VECTOR3_ARRAY"
types[TYPE_PACKED_COLOR_ARRAY] = "TYPE_PACKED_COLOR_ARRAY"
types[TYPE_MAX] = "TYPE_MAX"
types[TYPE_DICTIONARY] = 'Dictionary'
types[TYPE_ARRAY] = 'Array'
types[TYPE_PACKED_BYTE_ARRAY] = 'TYPE_PACKED_BYTE_ARRAY'
types[TYPE_PACKED_INT32_ARRAY] = 'TYPE_PACKED_INT32_ARRAY'
types[TYPE_PACKED_FLOAT32_ARRAY] = 'TYPE_PACKED_FLOAT32_ARRAY'
types[TYPE_PACKED_STRING_ARRAY] = 'TYPE_PACKED_STRING_ARRAY'
types[TYPE_PACKED_VECTOR2_ARRAY] = 'TYPE_PACKED_VECTOR2_ARRAY'
types[TYPE_PACKED_VECTOR3_ARRAY] = 'TYPE_PACKED_VECTOR3_ARRAY'
types[TYPE_PACKED_COLOR_ARRAY] = 'TYPE_PACKED_COLOR_ARRAY'
types[TYPE_MAX] = 'TYPE_MAX'
types[TYPE_STRING_NAME] = 'TYPE_STRING_NAME'
# Types to not be formatted when using _str
var _str_ignore_types = [TYPE_INT, TYPE_FLOAT, TYPE_STRING, TYPE_NIL, TYPE_BOOL]
var _str_ignore_types = [
TYPE_INT, TYPE_FLOAT, TYPE_STRING,
TYPE_NIL, TYPE_BOOL
]
func _init():
_init_types_dictionary()
# NativeScript does not exist when GDNative is not included in the build
if type_exists("NativeScript"):
var getter = load("res://addons/gut/get_native_script.gd")
NativeScriptClass = getter.get_it()
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
func _get_filename(path):
return path.split("/")[-1]
return path.split('/')[-1]
# ------------------------------------------------------------------------------
# Gets the filename of an object passed in. This does not return the
@ -64,35 +60,29 @@ func _get_filename(path):
func _get_obj_filename(thing):
var filename = null
if (
thing == null
or !is_instance_valid(thing)
or str(thing) == "[Object:null]"
or typeof(thing) != TYPE_OBJECT
or thing.has_method("__gut_instance_from_id")
):
if(thing == null or
_utils.is_native_class(thing) or
!is_instance_valid(thing) or
str(thing) == '<Object#null>' or
typeof(thing) != TYPE_OBJECT or
_utils.is_double(thing)):
return
if thing.get_script() == null:
if thing is PackedScene:
if(thing.get_script() == null):
if(thing is PackedScene):
filename = _get_filename(thing.resource_path)
else:
# If it isn't a packed scene and it doesn't have a script then
# we do nothing. This just read better.
# we do nothing. This just reads better.
pass
elif NativeScriptClass != null and thing.get_script() is NativeScriptClass:
# Work with GDNative scripts:
# inst_to_dict fails with "Not a script with an instance" on GDNative script instances
filename = _get_filename(thing.get_script().resource_path)
elif !_utils.is_native_class(thing):
elif(!_utils.is_native_class(thing)):
var dict = inst_to_dict(thing)
filename = _get_filename(dict["@path"])
if dict["@subpath"] != "":
filename += str("/", dict["@subpath"])
filename = _get_filename(dict['@path'])
if(str(dict['@subpath']) != ''):
filename += str('/', dict['@subpath'])
return filename
# ------------------------------------------------------------------------------
# Better object/thing to string conversion. Includes extra details about
# whatever is passed in when it can/should.
@ -101,50 +91,49 @@ func type2str(thing):
var filename = _get_obj_filename(thing)
var str_thing = str(thing)
if thing == null:
if(thing == null):
# According to str there is a difference between null and an Object
# that is somehow null. To avoid getting '[Object:null]' as output
# always set it to str(null) instead of str(thing). A null object
# will pass typeof(thing) == TYPE_OBJECT check so this has to be
# before that.
str_thing = str(null)
elif typeof(thing) == TYPE_FLOAT:
if !"." in str_thing:
str_thing += ".0"
elif typeof(thing) == TYPE_STRING:
elif(typeof(thing) == TYPE_FLOAT):
if(!'.' in str_thing):
str_thing += '.0'
elif(typeof(thing) == TYPE_STRING):
str_thing = str('"', thing, '"')
elif typeof(thing) in _str_ignore_types:
elif(typeof(thing) in _str_ignore_types):
# do nothing b/c we already have str(thing) in
# to_return. I think this just reads a little
# better this way.
pass
elif typeof(thing) == TYPE_OBJECT:
if _utils.is_native_class(thing):
elif(typeof(thing) == TYPE_OBJECT):
if(_utils.is_native_class(thing)):
str_thing = _utils.get_native_class_name(thing)
elif _utils.is_double(thing):
var double_path = _get_filename(thing.__gut_metadata_.path)
if thing.__gut_metadata_.subpath != "":
double_path += str("/", thing.__gut_metadata_.subpath)
elif thing.__gut_metadata_.from_singleton != "":
double_path = thing.__gut_metadata_.from_singleton + " Singleton"
elif(_utils.is_double(thing)):
var double_path = _get_filename(thing.__gutdbl.thepath)
if(thing.__gutdbl.subpath != ''):
double_path += str('/', thing.__gutdbl.subpath)
elif(thing.__gutdbl.from_singleton != ''):
double_path = thing.__gutdbl.from_singleton + " Singleton"
var double_type = "double"
if thing.__gut_metadata_.is_partial:
if(thing.__gutdbl.is_partial):
double_type = "partial-double"
str_thing += str("(", double_type, " of ", double_path, ")")
filename = null
elif types.has(typeof(thing)):
if !str_thing.begins_with("("):
str_thing = "(" + str_thing + ")"
elif(types.has(typeof(thing))):
if(!str_thing.begins_with('(')):
str_thing = '(' + str_thing + ')'
str_thing = str(types[typeof(thing)], str_thing)
if filename != null:
str_thing += str("(", filename, ")")
if(filename != null):
str_thing += str('(', filename, ')')
return str_thing
# ------------------------------------------------------------------------------
# Returns the string truncated with an '...' in it. Shows the start and last
# 10 chars. If the string is smaller than max_size the entire string is
@ -152,31 +141,28 @@ func type2str(thing):
# ------------------------------------------------------------------------------
func truncate_string(src, max_size):
var to_return = src
if src.length() > max_size - 10 and max_size != -1:
to_return = str(
src.substr(0, max_size - 10), "...", src.substr(src.length() - 10, src.length())
)
if(src.length() > max_size - 10 and max_size != -1):
to_return = str(src.substr(0, max_size - 10), '...', src.substr(src.length() - 10, src.length()))
return to_return
func _get_indent_text(times, pad):
var to_return = ""
var to_return = ''
for i in range(times):
to_return += pad
return to_return
func indent_text(text, times, pad):
if times == 0:
if(times == 0):
return text
var to_return = text
var ending_newline = ""
var ending_newline = ''
if text.ends_with("\n"):
if(text.ends_with("\n")):
ending_newline = "\n"
to_return = to_return.left(to_return.length() - 1)
to_return = to_return.left(to_return.length() -1)
var padding = _get_indent_text(times, pad)
to_return = to_return.replace("\n", "\n" + padding)

View file

@ -1,10 +1,15 @@
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _lgr = _utils.get_logger()
var return_val = null
var stub_target = null
var target_subpath = null
# the parameter values to match method call on.
var parameters = null
var stub_method = null
var call_super = false
# Whether this is a stub for default parameter values as they are defined in
# the script, and not an overridden default value.
var is_script_default = false
# -- Paramter Override --
# Parmater overrides are stored in here along with all the other stub info
@ -22,48 +27,66 @@ var parameter_defaults = null
var _parameter_override_only = true
# --
const NOT_SET = "|_1_this_is_not_set_1_|"
const NOT_SET = '|_1_this_is_not_set_1_|'
func _init(target = null,method = null,subpath = null):
func _init(target=null,method=null,subpath=null):
stub_target = target
stub_method = method
target_subpath = subpath
if(typeof(target) == TYPE_STRING):
if(target.is_absolute_path()):
stub_target = load(str(target))
else:
_lgr.warn(str(target, ' is not a valid path'))
if(stub_target is PackedScene):
stub_target = _utils.get_scene_script_object(stub_target)
elif(_utils.is_native_class(stub_target)):
print("Got a native class: ", stub_target)
# this is used internally to stub default parameters for everything that is
# doubled...or something. Look for stub_defaults_from_meta for usage. This
# behavior is not to be used by end users.
if(typeof(method) == TYPE_DICTIONARY):
_load_defaults_from_metadata(method)
func _load_defaults_from_metadata(meta):
stub_method = meta.name
var values = meta.default_args.duplicate()
while (values.size() < meta.args.size()):
values.push_front(null)
param_defaults(values)
func to_return(val):
return_val = val
call_super = false
_parameter_override_only = false
if(stub_method == '_init'):
_lgr.error("You cannot stub _init to do nothing. Super's _init is always called.")
else:
return_val = val
call_super = false
_parameter_override_only = false
return self
func to_do_nothing():
return to_return(null)
func to_call_super():
call_super = true
_parameter_override_only = false
to_return(null)
return self
func when_passed(
p1 = NOT_SET,
p2 = NOT_SET,
p3 = NOT_SET,
p4 = NOT_SET,
p5 = NOT_SET,
p6 = NOT_SET,
p7 = NOT_SET,
p8 = NOT_SET,
p9 = NOT_SET,
p10 = NOT_SET
):
parameters = [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10]
func to_call_super():
if(stub_method == '_init'):
_lgr.error("You cannot stub _init to call super. Super's _init is always called.")
else:
call_super = true
_parameter_override_only = false
return self
func when_passed(p1=NOT_SET,p2=NOT_SET,p3=NOT_SET,p4=NOT_SET,p5=NOT_SET,p6=NOT_SET,p7=NOT_SET,p8=NOT_SET,p9=NOT_SET,p10=NOT_SET):
parameters = [p1,p2,p3,p4,p5,p6,p7,p8,p9,p10]
var idx = 0
while idx < parameters.size():
if str(parameters[idx]) == NOT_SET:
while(idx < parameters.size()):
if(str(parameters[idx]) == NOT_SET):
parameters.remove_at(idx)
else:
idx += 1
@ -87,31 +110,28 @@ func has_param_override():
func is_param_override_only():
var to_return = false
if has_param_override():
if(has_param_override()):
to_return = _parameter_override_only
return to_return
func to_s():
var base_string = str(stub_target)
if target_subpath != null:
base_string += str("[", target_subpath, "].")
else:
base_string += "."
base_string += stub_method
var base_string = str(stub_target, '.', stub_method)
if has_param_override():
base_string += str(
" (param count override=", parameter_count, " defaults=", parameter_defaults
)
if is_param_override_only():
if(has_param_override()):
base_string += str(' (param count override=', parameter_count, ' defaults=', parameter_defaults)
if(is_param_override_only()):
base_string += " ONLY"
base_string += ") "
if(is_script_default):
base_string += " script default"
base_string += ') '
if call_super:
if(call_super):
base_string += " to call SUPER"
if parameters != null:
base_string += str(" with params (", parameters, ") returns ", return_val)
if(parameters != null):
base_string += str(' with params (', parameters, ') returns ', return_val)
else:
base_string += str(' returns ', return_val)
return base_string

View file

@ -12,124 +12,121 @@
# }
# }
var returns = {}
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _lgr = _utils.get_logger()
var _strutils = _utils.Strutils.new()
var _class_db_name_hash = {}
func _init():
_class_db_name_hash = _make_crazy_dynamic_over_engineered_class_db_hash()
# So, I couldn't figure out how to get to a reference for a GDNative Class
# using a string. ClassDB has all thier names...so I made a hash using those
# names and the classes. Then I dynmaically make a script that has that as
# the source and grab the hash out of it and return it. Super Rube Golbergery,
# but tons of fun.
func _make_crazy_dynamic_over_engineered_class_db_hash():
var text = "var all_the_classes = {\n"
for classname in ClassDB.get_class_list():
if(ClassDB.can_instantiate(classname)):
text += str('"', classname, '": ', classname, ", \n")
else:
text += str('# ', classname, "\n")
text += "}"
var inst = _utils.create_script_from_source(text).new()
return inst.all_the_classes
func _make_key_from_metadata(doubled):
var to_return = doubled.__gut_metadata_.path
func _find_matches(obj, method):
var matches = null
var last_not_null_parent = null
if doubled.__gut_metadata_.from_singleton != "":
to_return = str(doubled.__gut_metadata_.from_singleton)
elif doubled.__gut_metadata_.subpath != "":
to_return += str("-", doubled.__gut_metadata_.subpath)
# Search for what is passed in first. This could be a class or an instance.
# We want to find the instance before we find the class. If we do not have
# an entry for the instance then see if we have an entry for the class.
if(returns.has(obj) and returns[obj].has(method)):
matches = returns[obj][method]
elif(_utils.is_instance(obj)):
var parent = obj.get_script()
var found = false
while(parent != null and !found):
found = returns.has(parent)
return to_return
if(!found):
last_not_null_parent = parent
parent = parent.get_base_script()
# Could not find the script so check to see if a native class of this
# type was stubbed.
if(!found):
var base_type = last_not_null_parent.get_instance_base_type()
if(_class_db_name_hash.has(base_type)):
parent = _class_db_name_hash[base_type]
found = returns.has(parent)
# Creates they key for the returns hash based on the type of object passed in
# obj could be a string of a path to a script with an optional subpath or
# it could be an instance of a doubled object.
func _make_key_from_variant(obj, subpath = null):
var to_return = null
if(found and returns[parent].has(method)):
matches = returns[parent][method]
match typeof(obj):
TYPE_STRING:
# this has to match what is done in _make_key_from_metadata
to_return = obj
if subpath != null and subpath != "":
to_return += str("-", subpath)
TYPE_OBJECT:
if _utils.is_instance(obj):
to_return = _make_key_from_metadata(obj)
elif _utils.is_native_class(obj):
to_return = _utils.get_native_class_name(obj)
else:
to_return = obj.resource_path
return to_return
func _add_obj_method(obj, method, subpath = null):
var key = _make_key_from_variant(obj, subpath)
if _utils.is_instance(obj):
key = obj
if !returns.has(key):
returns[key] = {}
if !returns[key].has(method):
returns[key][method] = []
return key
# ##############
# Public
# ##############
return matches
# Searches returns for an entry that matches the instance or the class that
# passed in obj is.
#
# obj can be an instance, class, or a path.
func _find_stub(obj, method, parameters = null, find_overloads = false):
var key = _make_key_from_variant(obj)
func _find_stub(obj, method, parameters=null, find_overloads=false):
var to_return = null
var matches = _find_matches(obj, method)
if _utils.is_instance(obj):
if returns.has(obj) and returns[obj].has(method):
key = obj
elif obj.get("__gut_metadata_"):
key = _make_key_from_metadata(obj)
if(matches == null):
return null
if returns.has(key) and returns[key].has(method):
var param_match = null
var null_match = null
var overload_match = null
var param_match = null
var null_match = null
var overload_match = null
for i in range(returns[key][method].size()):
if returns[key][method][i].parameters == parameters:
param_match = returns[key][method][i]
for i in range(matches.size()):
var cur_stub = matches[i]
if(cur_stub.parameters == parameters):
param_match = cur_stub
if returns[key][method][i].parameters == null:
null_match = returns[key][method][i]
if(cur_stub.parameters == null and !cur_stub.is_param_override_only()):
null_match = cur_stub
if returns[key][method][i].has_param_override():
overload_match = returns[key][method][i]
if(cur_stub.has_param_override()):
if(overload_match == null || overload_match.is_script_default):
overload_match = cur_stub
if find_overloads and overload_match != null:
to_return = overload_match
# We have matching parameter values so return the stub value for that
elif param_match != null:
to_return = param_match
# We found a case where the parameters were not specified so return
# parameters for that. Only do this if the null match is not *just*
# a paramerter override stub.
elif null_match != null and !null_match.is_param_override_only():
to_return = null_match
else:
_lgr.warn(
str(
"Call to [",
method,
"] was not stubbed for the supplied parameters ",
parameters,
". Null was returned."
)
)
if(find_overloads and overload_match != null):
to_return = overload_match
# We have matching parameter values so return the stub value for that
elif(param_match != null):
to_return = param_match
# We found a case where the parameters were not specified so return
# parameters for that. Only do this if the null match is not *just*
# a paramerter override stub.
elif(null_match != null):
to_return = null_match
return to_return
# ##############
# Public
# ##############
func add_stub(stub_params):
if stub_params.stub_method == "_init":
_lgr.error("You cannot stub _init. Super's _init is ALWAYS called.")
else:
var key = _add_obj_method(
stub_params.stub_target, stub_params.stub_method, stub_params.target_subpath
)
returns[key][stub_params.stub_method].append(stub_params)
stub_params._lgr = _lgr
var key = stub_params.stub_target
if(!returns.has(key)):
returns[key] = {}
if(!returns[key].has(stub_params.stub_method)):
returns[key][stub_params.stub_method] = []
returns[key][stub_params.stub_method].append(stub_params)
# Gets a stubbed return value for the object and method passed in. If the
@ -147,33 +144,34 @@ func add_stub(stub_params):
# obj: this should be an instance of a doubled object.
# method: the method called
# parameters: optional array of parameter vales to find a return value for.
func get_return(obj, method, parameters = null):
func get_return(obj, method, parameters=null):
var stub_info = _find_stub(obj, method, parameters)
if stub_info != null:
if(stub_info != null):
return stub_info.return_val
else:
_lgr.warn(str('Call to [', method, '] was not stubbed for the supplied parameters ', parameters, '. Null was returned.'))
return null
func should_call_super(obj, method, parameters = null):
if _utils.non_super_methods.has(method):
func should_call_super(obj, method, parameters=null):
if(_utils.non_super_methods.has(method)):
return false
var stub_info = _find_stub(obj, method, parameters)
var is_partial = false
if typeof(obj) != TYPE_STRING: # some stubber tests test with strings
is_partial = obj.__gut_metadata_.is_partial
if(typeof(obj) != TYPE_STRING): # some stubber tests test with strings
is_partial = obj.__gutdbl.is_partial
var should = is_partial
if stub_info != null:
if(stub_info != null):
should = stub_info.call_super
elif !is_partial:
elif(!is_partial):
# this log message is here because of how the generated doubled scripts
# are structured. With this log msg here, you will only see one
# "unstubbed" info instead of multiple.
_lgr.info("Unstubbed call to " + method + "::" + _strutils.type2str(obj))
_lgr.info('Unstubbed call to ' + method + '::' + _strutils.type2str(obj))
should = false
return should
@ -183,7 +181,7 @@ func get_parameter_count(obj, method):
var to_return = null
var stub_info = _find_stub(obj, method, null, true)
if stub_info != null and stub_info.has_param_override():
if(stub_info != null and stub_info.has_param_override()):
to_return = stub_info.parameter_count
return to_return
@ -192,11 +190,11 @@ func get_parameter_count(obj, method):
func get_default_value(obj, method, p_index):
var to_return = null
var stub_info = _find_stub(obj, method, null, true)
if (
stub_info != null
and stub_info.parameter_defaults != null
and stub_info.parameter_defaults.size() > p_index
):
if(stub_info != null and
stub_info.parameter_defaults != null and
stub_info.parameter_defaults.size() > p_index):
to_return = stub_info.parameter_defaults[p_index]
return to_return
@ -215,7 +213,7 @@ func set_logger(logger):
func to_s():
var text = ""
var text = ''
for thing in returns:
text += str("-- ", thing, " --\n")
for method in returns[thing]:
@ -223,7 +221,13 @@ func to_s():
for i in range(returns[thing][method].size()):
text += "\t\t" + returns[thing][method][i].to_s() + "\n"
if text == "":
text = "Stubber is empty"
if(text == ''):
text = 'Stubber is empty';
return text
func stub_defaults_from_meta(target, method_meta):
var params = _utils.StubParams.new(target, method_meta)
params.is_script_default = true
add_stub(params)

View file

@ -1,42 +1,64 @@
# ------------------------------------------------------------------------------
# Contains all the results of a single test. Allows for multiple asserts results
# and pending calls.
#
# When determining the status of a test, check for failing then passing then
# pending.
# ------------------------------------------------------------------------------
class Test:
var pass_texts = []
var fail_texts = []
var pending_texts = []
var orphans = 0
var line_number = 0
# must have passed an assert and not have any other status to be passing
func is_passing():
return pass_texts.size() > 0 and fail_texts.size() == 0 and pending_texts.size() == 0
# failing takes precedence over everything else, so any failures makes the
# test a failure.
func is_failing():
return fail_texts.size() > 0
# test is only pending if pending was called and the test is not failing.
func is_pending():
return pending_texts.size() > 0 and fail_texts.size() == 0
func did_something():
return is_passing() or is_failing() or is_pending()
# NOTE: The "failed" and "pending" text must match what is outputted by
# the logger in order for text highlighting to occur in summary.
func to_s():
var pad = " "
var to_return = ""
var pad = ' '
var to_return = ''
for i in range(fail_texts.size()):
to_return += str(pad, "[Failed]: ", fail_texts[i], "\n")
to_return += str(pad, '[Failed]: ', fail_texts[i], "\n")
for i in range(pending_texts.size()):
to_return += str(pad, "[Pending]: ", pending_texts[i], "\n")
to_return += str(pad, '[Pending]: ', pending_texts[i], "\n")
return to_return
func get_status():
var to_return = "no asserts"
if pending_texts.size() > 0:
to_return = "pending"
elif fail_texts.size() > 0:
to_return = "fail"
elif pass_texts.size() > 0:
to_return = "pass"
var to_return = 'no asserts'
if(pending_texts.size() > 0):
to_return = 'pending'
elif(fail_texts.size() > 0):
to_return = 'fail'
elif(pass_texts.size() > 0):
to_return = 'pass'
return to_return
# ------------------------------------------------------------------------------
# Contains all the results for a single test-script/inner class. Persists the
# names of the tests and results and the order in which the tests were run.
# ------------------------------------------------------------------------------
class TestScript:
var name = "NOT_SET"
var name = 'NOT_SET'
var was_skipped = false
var skip_reason = ''
var _tests = {}
var _test_order = []
@ -64,22 +86,37 @@ class TestScript:
func get_passing_test_count():
var count = 0
for key in _tests:
if _tests[key].fail_texts.size() == 0 and _tests[key].pending_texts.size() == 0:
if(_tests[key].is_passing()):
count += 1
return count
func get_failing_test_count():
var count = 0
for key in _tests:
if _tests[key].fail_texts.size() != 0:
if(_tests[key].is_failing()):
count += 1
return count
func get_risky_count():
var count = 0
if(was_skipped):
count = 1
else:
for key in _tests:
if(!_tests[key].did_something()):
count += 1
return count
func get_test_obj(obj_name):
if !_tests.has(obj_name):
_tests[obj_name] = Test.new()
if(!_tests.has(obj_name)):
var to_add = Test.new()
_tests[obj_name] = to_add
_test_order.append(obj_name)
return _tests[obj_name]
var to_return = _tests[obj_name]
return to_return
func add_pass(test_name, reason):
var t = get_test_obj(test_name)
@ -96,7 +133,6 @@ class TestScript:
func get_tests():
return _tests
# ------------------------------------------------------------------------------
# Summary Class
#
@ -105,58 +141,53 @@ class TestScript:
# ------------------------------------------------------------------------------
var _scripts = []
func add_script(name):
_scripts.append(TestScript.new(name))
func get_scripts():
return _scripts
func get_current_script():
return _scripts[_scripts.size() - 1]
func add_test(test_name):
# print('-- test_name = ', test_name)
# print('-- current script = ', get_current_script())
# print('-- test_obj = ', get_current_script().get_test_obj(test_name))
return get_current_script().get_test_obj(test_name)
func add_pass(test_name, reason = ""):
func add_pass(test_name, reason = ''):
get_current_script().add_pass(test_name, reason)
func add_fail(test_name, reason = ""):
func add_fail(test_name, reason = ''):
get_current_script().add_fail(test_name, reason)
func add_pending(test_name, reason = ""):
func add_pending(test_name, reason = ''):
get_current_script().add_pending(test_name, reason)
func get_test_text(test_name):
return test_name + "\n" + get_current_script().get_test_obj(test_name).to_s()
# Gets the count of unique script names minus the .<Inner Class Name> at the
# end. Used for displaying the number of scripts without including all the
# Inner Classes.
func get_non_inner_class_script_count():
var unique_scripts = {}
var counter = load('res://addons/gut/thing_counter.gd').new()
for i in range(_scripts.size()):
var ext_loc = _scripts[i].name.find_last(".gd.")
if ext_loc == -1:
unique_scripts[_scripts[i].name] = 1
else:
unique_scripts[_scripts[i].name.substr(0, ext_loc + 3)] = 1
return unique_scripts.keys().size()
var ext_loc = _scripts[i].name.rfind('.gd.')
var to_add = _scripts[i].name
if(ext_loc != -1):
to_add = _scripts[i].name.substr(0, ext_loc + 3)
counter.add(to_add)
return counter.get_unique_count()
func get_totals():
var totals = {
passing = 0,
pending = 0,
failing = 0,
risky = 0,
tests = 0,
scripts = 0,
passing_tests = 0,
@ -164,12 +195,16 @@ func get_totals():
}
for i in range(_scripts.size()):
# assert totals
totals.passing += _scripts[i].get_pass_count()
totals.pending += _scripts[i].get_pending_count()
totals.failing += _scripts[i].get_fail_count()
# test totals
totals.tests += _scripts[i]._test_order.size()
totals.passing_tests += _scripts[i].get_passing_test_count()
totals.failing_tests += _scripts[i].get_failing_test_count()
totals.risky += _scripts[i].get_risky_count()
totals.scripts = get_non_inner_class_script_count()
@ -182,34 +217,50 @@ func log_summary_text(lgr):
for s in range(_scripts.size()):
lgr.set_indent_level(0)
if _scripts[s].get_fail_count() > 0 or _scripts[s].get_pending_count() > 0:
if(_scripts[s].was_skipped or _scripts[s].get_fail_count() > 0 or _scripts[s].get_pending_count() > 0):
lgr.log(_scripts[s].name, lgr.fmts.underline)
if(_scripts[s].was_skipped):
lgr.inc_indent()
var skip_msg = str('[Risky] Script was skipped: ', _scripts[s].skip_reason)
lgr.log(skip_msg, lgr.fmts.yellow)
lgr.dec_indent()
for t in range(_scripts[s]._test_order.size()):
var tname = _scripts[s]._test_order[t]
var test = _scripts[s].get_test_obj(tname)
if test.fail_texts.size() > 0 or test.pending_texts.size() > 0:
if(!test.is_passing()):
found_failing_or_pending = true
lgr.log(str("- ", tname))
lgr.log(str('- ', tname))
lgr.inc_indent()
for i in range(test.fail_texts.size()):
lgr.failed(test.fail_texts[i])
for i in range(test.pending_texts.size()):
lgr.pending(test.pending_texts[i])
if(!test.did_something()):
lgr.log('[Risky] Did not assert', lgr.fmts.yellow)
lgr.dec_indent()
lgr.set_indent_level(0)
if !found_failing_or_pending:
lgr.log("All tests passed", lgr.fmts.green)
if(!found_failing_or_pending):
lgr.log('All tests passed', lgr.fmts.green)
# just picked a non-printable char, dunno if it is a good or bad choice.
var npws = PackedByteArray([31]).get_string_from_ascii()
lgr.log()
var _totals = get_totals()
lgr.log("Totals", lgr.fmts.yellow)
lgr.log(str("Scripts: ", get_non_inner_class_script_count()))
lgr.log(str("Passing tests ", _totals.passing_tests))
lgr.log(str("Failing tests ", _totals.failing_tests))
lgr.log(str("Pending: ", _totals.pending))
lgr.log(str("Asserts: ", _totals.passing, "/", _totals.failing))
lgr.log(str('Scripts: ', get_non_inner_class_script_count()))
lgr.log(str('Passing tests ', _totals.passing_tests))
lgr.log(str('Failing tests ', _totals.failing_tests))
lgr.log(str('Risky tests ', _totals.risky))
var pnd=str('Pending: ', _totals.pending)
# add a non printable character so this "pending" isn't highlighted in the
# editor's output panel.
lgr.log(str(npws, pnd))
lgr.log(str('Asserts: ', _totals.passing, ' of ', _totals.passing + _totals.failing, ' passed'))
lgr.set_indent_level(orig_indent)

Some files were not shown because too many files have changed in this diff Show more