remove unit test framework

master
Fabien Freling 2020-02-24 23:08:03 +01:00
parent 61efafe9a3
commit 5828df43e2
29 changed files with 0 additions and 5720 deletions

View File

@ -1,348 +0,0 @@
extends Panel
onready var _script_list = $ScriptsList
onready var _nav = {
prev = $Navigation/Previous,
next = $Navigation/Next,
run = $Navigation/Run,
current_script = $Navigation/CurrentScript,
show_scripts = $Navigation/ShowScripts
}
onready var _progress = {
script = $ScriptProgress,
test = $TestProgress
}
onready var _summary = {
failing = $Summary/Failing,
passing = $Summary/Passing
}
onready var _extras = $ExtraOptions
onready var _ignore_pauses = $ExtraOptions/IgnorePause
onready var _continue_button = $Continue/Continue
onready var _text_box = $TextDisplay/RichTextLabel
onready var _titlebar = {
bar = $TitleBar,
time = $TitleBar/Time,
label = $TitleBar/Title
}
var _mouse = {
down = false,
in_title = false,
down_pos = null,
in_handle = false
}
var _is_running = false
var _start_time = 0.0
var _time = 0.0
const DEFAULT_TITLE = 'Gut: The Godot Unit Testing tool.'
var _utils = load('res://addons/gut/utils.gd').new()
var _text_box_blocker_enabled = true
var _pre_maximize_size = null
signal end_pause
signal ignore_pause
signal log_level_changed
signal run_script
signal run_single_script
signal script_selected
func _ready():
_pre_maximize_size = rect_size
_hide_scripts()
_update_controls()
_nav.current_script.set_text("No scripts available")
set_title()
clear_summary()
$TitleBar/Time.set_text("")
$ExtraOptions/DisableBlocker.pressed = !_text_box_blocker_enabled
_extras.visible = false
update()
func _process(delta):
if(_is_running):
_time = OS.get_unix_time() - _start_time
var disp_time = round(_time * 100)/100
$TitleBar/Time.set_text(str(disp_time))
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)
for i in range(1, 10):
var x = rect_size - Vector2(i * line_space, grab_margin)
var y = rect_size - Vector2(grab_margin, i * line_space)
draw_line(x, y, grab_line_color, 1, true)
func _on_Maximize_draw():
# draw the maximize square thing.
var btn = $TitleBar/Maximize
btn.set_text('')
var w = btn.get_size().x
var h = btn.get_size().y
btn.draw_rect(Rect2(0, 0, w, h), Color(0, 0, 0, 1))
btn.draw_rect(Rect2(2, 4, w - 4, h - 6), Color(1,1,1,1))
func _on_ShowExtras_draw():
var btn = $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():
_run_mode()
emit_signal('run_single_script', get_selected_index())
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', $LogLevelSlider.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_ShowScripts_pressed():
_toggle_scripts()
func _on_ScriptsList_item_selected(index):
_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
if(_mouse.in_handle):
if(event is InputEventMouseMotion and _mouse.down):
var new_size = rect_size + event.position - _mouse.down_pos
var new_mouse_down_pos = event.position
rect_size = new_size
_mouse.down_pos = new_mouse_down_pos
_pre_maximize_size = rect_size
func _on_ResizeHandle_mouse_entered():
_mouse.in_handle = true
func _on_ResizeHandle_mouse_exited():
_mouse.in_handle = false
# Send scroll type events through to the text box
func _on_FocusBlocker_gui_input(ev):
if(_text_box_blocker_enabled):
if(ev is InputEventPanGesture):
get_text_box()._gui_input(ev)
# convert a drag into a pan gesture so it scrolls.
elif(ev is InputEventScreenDrag):
var converted = InputEventPanGesture.new()
converted.delta = Vector2(0, ev.relative.y)
converted.position = Vector2(0, 0)
get_text_box()._gui_input(converted)
elif(ev is InputEventMouseButton and (ev.button_index == BUTTON_WHEEL_DOWN or ev.button_index == BUTTON_WHEEL_UP)):
get_text_box()._gui_input(ev)
else:
get_text_box()._gui_input(ev)
print(ev)
func _on_RichTextLabel_gui_input(ev):
pass
# leaving this b/c it is wired up and might have to send
# more signals through
print(ev)
func _on_Copy_pressed():
_text_box.select_all()
_text_box.copy()
_text_box.deselect()
func _on_DisableBlocker_toggled(button_pressed):
_text_box_blocker_enabled = !button_pressed
func _on_ShowExtras_toggled(button_pressed):
_extras.visible = button_pressed
func _on_Maximize_pressed():
if(rect_size == _pre_maximize_size):
maximize()
else:
rect_size = _pre_maximize_size
# ####################
# Private
# ####################
func _run_mode(is_running=true):
if(is_running):
_start_time = OS.get_unix_time()
_time = _start_time
_summary.failing.set_text("0")
_summary.passing.set_text("0")
_is_running = is_running
_hide_scripts()
var ctrls = $Navigation.get_children()
for i in range(ctrls.size()):
ctrls[i].disabled = is_running
func _select_script(index):
$Navigation/CurrentScript.set_text(_script_list.get_item_text(index))
_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.show_scripts.disabled = is_empty
# ####################
# 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 $LogLevelSlider.value
func set_log_level(value):
$LogLevelSlider.value = _utils.nvl(value, 0)
func set_ignore_pause(should):
_ignore_pauses.pressed = should
func get_ignore_pause():
return _ignore_pauses.pressed
func get_text_box():
return $TextDisplay/RichTextLabel
func end_run():
_run_mode(false)
_update_controls()
func set_progress_script_max(value):
_progress.script.set_max(value)
func set_progress_script_value(value):
_progress.script.set_value(value)
func set_progress_test_max(value):
_progress.test.set_max(value)
func set_progress_test_value(value):
_progress.test.set_value(value)
func clear_progress():
_progress.test.set_value(0)
_progress.script.set_value(0)
func pause():
print('we got here')
_continue_button.disabled = false
func set_title(title=null):
if(title == null):
$TitleBar/Title.set_text(DEFAULT_TITLE)
else:
$TitleBar/Title.set_text(title)
func get_run_duration():
return $TitleBar/Time.text.to_float()
func add_passing(amount=1):
if(!_summary):
return
_summary.passing.set_text(str(_summary.passing.get_text().to_int() + amount))
$Summary.show()
func add_failing(amount=1):
if(!_summary):
return
_summary.failing.set_text(str(_summary.failing.get_text().to_int() + amount))
$Summary.show()
func clear_summary():
_summary.passing.set_text("0")
_summary.failing.set_text("0")
$Summary.hide()
func maximize():
if(is_inside_tree()):
var vp_size_offset = get_viewport().size
rect_size = vp_size_offset / get_scale()
set_position(Vector2(0, 0))

View File

@ -1,306 +0,0 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://addons/gut/GutScene.gd" type="Script" id=1]
[sub_resource type="StyleBoxFlat" id=1]
bg_color = Color( 0.193863, 0.205501, 0.214844, 1 )
corner_radius_top_left = 20
corner_radius_top_right = 20
[sub_resource type="StyleBoxFlat" id=2]
bg_color = Color( 1, 1, 1, 1 )
border_color = Color( 0, 0, 0, 1 )
corner_radius_top_left = 5
corner_radius_top_right = 5
[sub_resource type="Theme" id=3]
resource_local_to_scene = true
Panel/styles/panel = SubResource( 2 )
Panel/styles/panelf = null
Panel/styles/panelnc = null
[node name="Gut" type="Panel"]
margin_right = 740.0
margin_bottom = 320.0
rect_min_size = Vector2( 740, 250 )
custom_styles/panel = SubResource( 1 )
script = ExtResource( 1 )
[node name="TitleBar" type="Panel" parent="."]
anchor_right = 1.0
margin_bottom = 40.0
theme = SubResource( 3 )
[node name="Title" type="Label" parent="TitleBar"]
anchor_right = 1.0
margin_bottom = 40.0
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "Gut"
align = 1
valign = 1
[node name="Time" type="Label" parent="TitleBar"]
anchor_left = 1.0
anchor_right = 1.0
margin_left = -114.0
margin_right = -53.0
margin_bottom = 40.0
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "9999.99"
valign = 1
[node name="Maximize" type="Button" parent="TitleBar"]
anchor_left = 1.0
anchor_right = 1.0
margin_left = -30.0
margin_top = 10.0
margin_right = -6.0
margin_bottom = 30.0
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "M"
flat = true
[node name="ScriptProgress" type="ProgressBar" parent="."]
editor/display_folded = true
anchor_top = 1.0
anchor_bottom = 1.0
margin_left = 70.0
margin_top = -100.0
margin_right = 180.0
margin_bottom = -70.0
step = 1.0
[node name="Label" type="Label" parent="ScriptProgress"]
margin_left = -70.0
margin_right = -10.0
margin_bottom = 24.0
text = "Script"
align = 1
valign = 1
[node name="TestProgress" type="ProgressBar" parent="."]
editor/display_folded = true
anchor_top = 1.0
anchor_bottom = 1.0
margin_left = 70.0
margin_top = -70.0
margin_right = 180.0
margin_bottom = -40.0
step = 1.0
[node name="Label" type="Label" parent="TestProgress"]
margin_left = -70.0
margin_right = -10.0
margin_bottom = 24.0
text = "Tests"
align = 1
valign = 1
[node name="TextDisplay" type="Panel" parent="."]
editor/display_folded = true
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = 40.0
margin_bottom = -107.0
__meta__ = {
"_edit_group_": true
}
[node name="RichTextLabel" type="TextEdit" parent="TextDisplay"]
anchor_right = 1.0
anchor_bottom = 1.0
mouse_default_cursor_shape = 0
readonly = true
syntax_highlighting = true
smooth_scrolling = true
[node name="FocusBlocker" type="Panel" parent="TextDisplay"]
self_modulate = Color( 1, 1, 1, 0 )
anchor_right = 1.0
anchor_bottom = 1.0
margin_right = -10.0
[node name="Navigation" type="Panel" parent="."]
editor/display_folded = true
self_modulate = Color( 1, 1, 1, 0 )
anchor_top = 1.0
anchor_bottom = 1.0
margin_left = 220.0
margin_top = -100.0
margin_right = 580.0
[node name="Previous" type="Button" parent="Navigation"]
margin_left = -30.0
margin_right = 50.0
margin_bottom = 40.0
text = "<"
[node name="Next" type="Button" parent="Navigation"]
margin_left = 230.0
margin_right = 310.0
margin_bottom = 40.0
text = ">"
[node name="Run" type="Button" parent="Navigation"]
margin_left = 60.0
margin_right = 220.0
margin_bottom = 40.0
text = "Run"
[node name="CurrentScript" type="Button" parent="Navigation"]
margin_left = -30.0
margin_top = 50.0
margin_right = 310.0
margin_bottom = 90.0
text = "res://test/unit/test_gut.gd"
clip_text = true
[node name="ShowScripts" type="Button" parent="Navigation"]
margin_left = 320.0
margin_top = 50.0
margin_right = 360.0
margin_bottom = 90.0
text = "..."
[node name="LogLevelSlider" type="HSlider" parent="."]
editor/display_folded = true
anchor_top = 1.0
anchor_bottom = 1.0
margin_left = 80.0
margin_top = -40.0
margin_right = 130.0
margin_bottom = -20.0
rect_scale = Vector2( 2, 2 )
max_value = 2.0
tick_count = 3
ticks_on_borders = true
[node name="Label" type="Label" parent="LogLevelSlider"]
margin_left = -35.0
margin_top = 5.0
margin_right = 25.0
margin_bottom = 25.0
rect_scale = Vector2( 0.5, 0.5 )
text = "Log Level"
align = 1
valign = 1
[node name="ScriptsList" type="ItemList" parent="."]
anchor_bottom = 1.0
margin_left = 180.0
margin_top = 40.0
margin_right = 620.0
margin_bottom = -108.0
allow_reselect = true
[node name="ExtraOptions" type="Panel" parent="."]
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -210.0
margin_top = -246.0
margin_bottom = -106.0
custom_styles/panel = SubResource( 1 )
[node name="IgnorePause" type="CheckBox" parent="ExtraOptions"]
margin_left = 10.0
margin_top = 10.0
margin_right = 128.0
margin_bottom = 34.0
rect_scale = Vector2( 1.5, 1.5 )
text = "Ignore Pauses"
[node name="DisableBlocker" type="CheckBox" parent="ExtraOptions"]
margin_left = 10.0
margin_top = 50.0
margin_right = 130.0
margin_bottom = 74.0
rect_scale = Vector2( 1.5, 1.5 )
text = "Selectable"
[node name="Copy" type="Button" parent="ExtraOptions"]
margin_left = 20.0
margin_top = 90.0
margin_right = 200.0
margin_bottom = 130.0
text = "Copy"
[node name="ResizeHandle" type="Control" parent="."]
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -40.0
margin_top = -40.0
[node name="Continue" type="Panel" parent="."]
self_modulate = Color( 1, 1, 1, 0 )
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -150.0
margin_top = -100.0
margin_right = -30.0
margin_bottom = -10.0
[node name="Continue" type="Button" parent="Continue"]
margin_top = 50.0
margin_right = 119.0
margin_bottom = 90.0
disabled = true
text = "Continue"
[node name="ShowExtras" type="Button" parent="Continue"]
margin_left = 50.0
margin_right = 120.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 35, 20 )
toggle_mode = true
text = "_"
[node name="Summary" type="Node2D" parent="."]
editor/display_folded = true
position = Vector2( 0, 3 )
[node name="Passing" type="Label" parent="Summary"]
margin_top = 10.0
margin_right = 40.0
margin_bottom = 24.0
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "0"
align = 1
valign = 1
[node name="Failing" type="Label" parent="Summary"]
margin_left = 40.0
margin_top = 10.0
margin_right = 80.0
margin_bottom = 24.0
custom_colors/font_color = Color( 0, 0, 0, 1 )
text = "0"
align = 1
valign = 1
[connection signal="mouse_entered" from="TitleBar" to="." method="_on_TitleBar_mouse_entered"]
[connection signal="mouse_exited" from="TitleBar" to="." method="_on_TitleBar_mouse_exited"]
[connection signal="draw" from="TitleBar/Maximize" to="." method="_on_Maximize_draw"]
[connection signal="pressed" from="TitleBar/Maximize" to="." method="_on_Maximize_pressed"]
[connection signal="gui_input" from="TextDisplay/RichTextLabel" to="." method="_on_RichTextLabel_gui_input"]
[connection signal="gui_input" from="TextDisplay/FocusBlocker" to="." method="_on_FocusBlocker_gui_input"]
[connection signal="pressed" from="Navigation/Previous" to="." method="_on_Previous_pressed"]
[connection signal="pressed" from="Navigation/Next" to="." method="_on_Next_pressed"]
[connection signal="pressed" from="Navigation/Run" to="." method="_on_Run_pressed"]
[connection signal="pressed" from="Navigation/CurrentScript" to="." method="_on_CurrentScript_pressed"]
[connection signal="pressed" from="Navigation/ShowScripts" to="." method="_on_ShowScripts_pressed"]
[connection signal="value_changed" from="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="toggled" from="ExtraOptions/DisableBlocker" to="." method="_on_DisableBlocker_toggled"]
[connection signal="pressed" from="ExtraOptions/Copy" to="." method="_on_Copy_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"]
[connection signal="pressed" from="Continue/Continue" to="." method="_on_Continue_pressed"]
[connection signal="draw" from="Continue/ShowExtras" to="." method="_on_ShowExtras_draw"]
[connection signal="toggled" from="Continue/ShowExtras" to="." method="_on_ShowExtras_toggled"]

View File

@ -1,22 +0,0 @@
The MIT License (MIT)
=====================
Copyright (c) 2018 Tom "Butch" Wesley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,487 +0,0 @@
# ------------------------------------------------------------------------------
# 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
]
var built_ins = []
var local_methods = []
var _method_names = []
func is_blacklisted(method_meta):
return _blacklist.find(method_meta.name) != -1
func _add_name_if_does_not_have(method_name):
var should_add = _method_names.find(method_name) == -1
if(should_add):
_method_names.append(method_name)
return should_add
func add_built_in_method(method_meta):
var did_add = _add_name_if_does_not_have(method_meta.name)
if(did_add and !is_blacklisted(method_meta)):
built_ins.append(method_meta)
func add_local_method(method_meta):
var did_add = _add_name_if_does_not_have(method_meta.name)
if(did_add):
local_methods.append(method_meta)
func to_s():
var text = "Locals\n"
for i in range(local_methods.size()):
text += str(" ", local_methods[i].name, "\n")
text += "Built-Ins\n"
for i in range(built_ins.size()):
text += str(" ", built_ins[i].name, "\n")
return text
# ------------------------------------------------------------------------------
# Helper class to deal with objects and inner classes.
# ------------------------------------------------------------------------------
class ObjectInfo:
var _path = null
var _subpaths = []
var _utils = load('res://addons/gut/utils.gd').new()
var _method_strategy = null
var make_partial_double = false
var scene_path = null
var _native_class = null
var _native_class_instance = null
func _init(path, subpath=null):
_path = path
if(subpath != null):
_subpaths = _utils.split_string(subpath, '/')
# Returns an instance of the class/inner class
func instantiate():
var to_return = null
if(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 _utils.join_array(_subpaths, '/')
func has_subpath():
return _subpaths.size() != 0
func get_extends_text():
var extend = null
if(is_native()):
extend = str("extends ", get_native_class_name())
else:
extend = str("extends '", get_path(), '\'')
if(has_subpath()):
extend += str('.', get_subpath().replace('/', '.'))
return extend
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
_native_class_instance = native_class.new()
_path = _native_class_instance.get_class()
func get_native_class_name():
return _native_class_instance.get_class()
# ------------------------------------------------------------------------------
# START Doubler
# ------------------------------------------------------------------------------
var _utils = load('res://addons/gut/utils.gd').new()
var _output_dir = null
var _double_count = 0 # used in making files names unique
var _use_unique_names = true
var _spy = null
var _ignored_methods = _utils.OneToMany.new()
var _stubber = _utils.Stubber.new()
var _lgr = _utils.get_logger()
var _method_maker = _utils.MethodMaker.new()
var _strategy = null
func _init(strategy=_utils.DOUBLE_STRATEGY.PARTIAL):
# make sure _method_maker gets logger too
set_logger(_utils.get_logger())
_strategy = strategy
# ###############
# Private
# ###############
func _get_indented_line(indents, text):
var to_return = ''
for i in range(indents):
to_return += "\t"
return str(to_return, text, "\n")
func _stub_to_call_super(obj_info, method_name):
var path = obj_info.get_path()
if(obj_info.scene_path != null):
path = obj_info.scene_path
var params = _utils.StubParams.new(path, method_name, obj_info.get_subpath())
params.to_call_super()
_stubber.add_stub(params)
func _write_file(obj_info, dest_path, override_path=null):
var script_methods = _get_methods(obj_info)
var metadata = _get_stubber_metadata_text(obj_info)
if(override_path):
metadata = _get_stubber_metadata_text(obj_info, override_path)
var f = File.new()
var f_result = f.open(dest_path, f.WRITE)
if(f_result != OK):
print('Error creating file ', dest_path)
print('Could not create double for :', obj_info.to_s())
return
f.store_string(str(obj_info.get_extends_text(), "\n"))
f.store_string(metadata)
for i in range(script_methods.local_methods.size()):
if(obj_info.make_partial_double):
_stub_to_call_super(obj_info, script_methods.local_methods[i].name)
f.store_string(_get_func_text(script_methods.local_methods[i]))
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]))
f.close()
func _double_scene_and_script(scene_info, dest_path):
var dir = Directory.new()
dir.copy(scene_info.get_path(), dest_path)
var inst = load(scene_info.get_path()).instance()
var script_path = null
if(inst.get_script()):
script_path = inst.get_script().get_path()
inst.free()
if(script_path):
var oi = ObjectInfo.new(script_path)
oi.set_method_strategy(scene_info.get_method_strategy())
oi.make_partial_double = scene_info.make_partial_double
oi.scene_path = scene_info.get_path()
var double_path = _double(oi, scene_info.get_path())
var dq = '"'
var f = File.new()
f.open(dest_path, f.READ)
var source = f.get_as_text()
f.close()
source = source.replace(dq + script_path + dq, dq + double_path + dq)
f.open(dest_path, f.WRITE)
f.store_string(source)
f.close()
return script_path
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()
# first pass is for local methods only
for i 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[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 i 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[i].flags != 65 and !_ignored_methods.has(object_info.get_path(), methods[i]['name'])):
script_methods.add_built_in_method(methods[i])
return script_methods
func _get_inst_id_ref_str(inst):
var ref_str = 'null'
if(inst):
ref_str = str('instance_from_id(', inst.get_instance_id(),')')
return ref_str
func _get_stubber_metadata_text(obj_info, override_path = null):
var path = obj_info.get_path()
if(override_path != null):
path = override_path
return "var __gut_metadata_ = {\n" + \
"\tpath='" + path + "',\n" + \
"\tsubpath='" + obj_info.get_subpath() + "',\n" + \
"\tstubber=" + _get_inst_id_ref_str(_stubber) + ",\n" + \
"\tspy=" + _get_inst_id_ref_str(_spy) + "\n" + \
"}\n"
func _get_spy_text(method_hash):
var txt = ''
if(_spy):
var called_with = _method_maker.get_spy_call_parameters_text(method_hash)
txt += "\t__gut_metadata_.spy.add_call(self, '" + method_hash.name + "', " + called_with + ")\n"
return txt
func _get_func_text(method_hash):
var ftxt = _method_maker.get_decleration_text(method_hash) + "\n"
var called_with = _method_maker.get_spy_call_parameters_text(method_hash)
ftxt += _get_spy_text(method_hash)
if(_stubber and method_hash.name != '_init'):
var call_method = _method_maker.get_super_call_text(method_hash)
ftxt += "\tif(__gut_metadata_.stubber.should_call_super(self, '" + method_hash.name + "', " + called_with + ")):\n"
ftxt += "\t\treturn " + call_method + "\n"
ftxt += "\telse:\n"
ftxt += "\t\treturn __gut_metadata_.stubber.get_return(self, '" + method_hash.name + "', " + called_with + ")\n"
else:
ftxt += "\tpass\n"
return ftxt
func _get_super_func_text(method_hash):
var call_method = _method_maker.get_super_call_text(method_hash)
var call_super_text = str("return ", call_method, "\n")
var ftxt = _method_maker.get_decleration_text(method_hash) + "\n"
ftxt += _get_spy_text(method_hash)
ftxt += _get_indented_line(1, call_super_text)
return ftxt
# returns the path to write the double file to
func _get_temp_path(object_info):
var file_name = null
var extension = null
if(object_info.is_native()):
file_name = object_info.get_native_class_name()
extension = 'gd'
else:
file_name = object_info.get_path().get_file().get_basename()
extension = object_info.get_path().get_extension()
if(object_info.has_subpath()):
file_name += '__' + object_info.get_subpath().replace('/', '__')
if(_use_unique_names):
file_name += str('__dbl', _double_count, '__.', extension)
else:
file_name += '.' + extension
var to_return = _output_dir.plus_file(file_name)
return to_return
func _double(obj_info, override_path=null):
var temp_path = _get_temp_path(obj_info)
_write_file(obj_info, temp_path, override_path)
_double_count += 1
return temp_path
func _double_script(path, make_partial, strategy):
var oi = ObjectInfo.new(path)
oi.make_partial_double = make_partial
oi.set_method_strategy(strategy)
var to_return = load(_double(oi))
return to_return
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
var to_return = load(_double(oi))
return to_return
func _double_scene(path, make_partial, strategy):
var oi = ObjectInfo.new(path)
oi.set_method_strategy(strategy)
oi.make_partial_double = make_partial
var temp_path = _get_temp_path(oi)
_double_scene_and_script(oi, temp_path)
return load(temp_path)
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
var to_return = load(_double(oi))
return to_return
# ###############
# Public
# ###############
func get_output_dir():
return _output_dir
func set_output_dir(output_dir):
_output_dir = output_dir
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 partial_double_scene(path, strategy=_strategy):
return _double_scene(path, true, strategy)
# double a scene
func double_scene(path, strategy=_strategy):
return _double_scene(path, false, strategy)
# double a script/object
func double(path, strategy=_strategy):
return _double_script(path, false, strategy)
func partial_double(path, strategy=_strategy):
return _double_script(path, true, strategy)
func partial_double_inner(path, subpath, strategy=_strategy):
return _double_inner(path, subpath, true, strategy)
# double an inner class in a script
func double_inner(path, subpath, strategy=_strategy):
return _double_inner(path, subpath, false, strategy)
# must always use FULL strategy since this is a native class and you won't get
# any methods if you don't use FULL
func double_gdnative(native_class):
return _double_gdnative(native_class, false, _utils.DOUBLE_STRATEGY.FULL)
# must always use FULL strategy since this is a native class and you won't get
# any methods if you don't use FULL
func partial_double_gdnative(native_class):
return _double_gdnative(native_class, true, _utils.DOUBLE_STRATEGY.FULL)
func clear_output_directory():
var did = false
if(_output_dir.find('user://') == 0):
var d = Directory.new()
var result = d.open(_output_dir)
# BIG GOTCHA HERE. If it cannot open the dir w/ erro 31, then the
# directory becomes res:// and things go on normally and gut clears out
# out res:// which is SUPER BAD.
if(result == OK):
d.list_dir_begin(true)
var f = d.get_next()
while(f != ''):
d.remove(f)
f = d.get_next()
did = true
return did
func delete_output_directory():
var did = clear_output_directory()
if(did):
var d = Directory.new()
d.remove(_output_dir)
# When creating doubles a unique name is used that each double can be its own
# thing. Sometimes, for testing, we do not want to do this so this allows
# you to turn off creating unique names for each double class.
#
# THIS SHOULD NEVER BE USED OUTSIDE OF INTERNAL GUT TESTING. It can cause
# weird, hard to track down problems.
func set_use_unique_names(should):
_use_unique_names = should
func add_ignored_method(path, method_name):
_ignored_methods.add(path, method_name)
func get_ignored_methods():
return _ignored_methods

File diff suppressed because it is too large Load Diff

View File

@ -1,356 +0,0 @@
################################################################################
#(G)odot (U)nit (T)est class
#
################################################################################
#The MIT License (MIT)
#=====================
#
#Copyright (c) 2019 Tom "Butch" Wesley
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
#
################################################################################
# Description
# -----------
# Command line interface for the GUT unit testing tool. Allows you to run tests
# from the command line instead of running a scene. Place this script along with
# gut.gd into your scripts directory at the root of your project. Once there you
# can run this script (from the root of your project) using the following command:
# godot -s -d test/gut/gut_cmdln.gd
#
# See the readme for a list of options and examples. You can also use the -gh
# option to get more information about how to use the command line interface.
#
# Version 6.8.1
################################################################################
extends SceneTree
var Optparse = load('res://addons/gut/optparse.gd')
var Gut = load('res://addons/gut/gut.gd')
#-------------------------------------------------------------------------------
# Helper class to resolve the various different places where an option can
# be set. Using the get_value method will enforce the order of precedence of:
# 1. command line value
# 2. config file value
# 3. default value
#
# The idea is that you set the base_opts. That will get you a copies of the
# hash with null values for the other types of values. Lower precedented hashes
# will punch through null values of higher precedented hashes.
#-------------------------------------------------------------------------------
class OptionResolver:
var base_opts = null
var cmd_opts = null
var config_opts = null
func get_value(key):
return _nvl(cmd_opts[key], _nvl(config_opts[key], base_opts[key]))
func set_base_opts(opts):
base_opts = opts
cmd_opts = _null_copy(opts)
config_opts = _null_copy(opts)
# creates a copy of a hash with all values null.
func _null_copy(h):
var new_hash = {}
for key in h:
new_hash[key] = null
return new_hash
func _nvl(a, b):
if(a == null):
return b
else:
return a
func _string_it(h):
var to_return = ''
for key in h:
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()))
func get_resolved_values():
var to_return = {}
for key in base_opts:
to_return[key] = get_value(key)
return to_return
func to_s_verbose():
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")
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').new()
# instance of gut
var _tester = null
# array of command line options specified
var _opts = []
# Hash for easier access to the options in the code. Options will be
# extracted into this hash and then the hash will be used afterwards so
# that I don't make any dumb typos and get the neat code-sense when I
# type a dot.
var options = {
config_file = 'res://.gutconfig.json',
dirs = [],
double_strategy = 'partial',
ignore_pause = false,
include_subdirs = false,
inner_class = '',
log_level = 1,
opacity = 100,
prefix = 'test_',
selected = '',
should_exit = false,
should_exit_on_success = false,
should_maximize = false,
show_help = false,
suffix = '.gd',
tests = [],
unit_test_name = '',
pre_run_script = '',
post_run_script = ''
}
# flag to indicate if only a single script should be run.
var _run_single = false
func setup_options():
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', [], 'Comma delimited list of directories to add tests from.')
opts.add('-gprefix', 'test_', 'Prefix used to find tests when specifying -gdir. Default "[default]"')
opts.add('-gsuffix', '.gd', 'Suffix used to find tests when specifying -gdir. Default "[default]"')
opts.add('-gmaximize', false, 'Maximizes test runner window to fit the viewport.')
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', 1, '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', 100, '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('-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.')
return opts
# Parses options, applying them to the _tester or setting values
# in the options struct.
func extract_command_line_options(from, to):
to.tests = from.get_value('-gtest')
to.dirs = from.get_value('-gdir')
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.log_level = from.get_value('-glog')
to.ignore_pause = from.get_value('-gignore_pause')
to.selected = from.get_value('-gselect')
to.prefix = from.get_value('-gprefix')
to.suffix = from.get_value('-gsuffix')
to.unit_test_name = from.get_value('-gunit_test_name')
to.config_file = from.get_value('-gconfig')
to.inner_class = from.get_value('-ginner_class')
to.opacity = from.get_value('-gopacity')
to.include_subdirs = from.get_value('-ginclude_subdirs')
to.double_strategy = from.get_value('-gdouble_strategy')
to.pre_run_script = from.get_value('-gpre_run_script')
to.post_run_script = from.get_value('-gpost_run_script')
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'):
print('ERROR: Config File "', file_path, '" does not exist.')
return -1
else:
return 1
f.open(file_path, f.READ)
var json = f.get_as_text()
f.close()
var results = JSON.parse(json)
# SHORTCIRCUIT
if(results.error != OK):
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)):
into[key] = results.result[key]
return 1
# Apply all the options specified to _tester. This is where the rubber meets
# the road.
func apply_options(opts):
_tester = Gut.new()
get_root().add_child(_tester)
_tester.connect('tests_finished', self, '_on_tests_finished', [opts.should_exit, opts.should_exit_on_success])
_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.set_include_subdirectories(opts.include_subdirs)
if(opts.should_maximize):
_tester.maximize()
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)
for i in range(opts.dirs.size()):
_tester.add_directory(opts.dirs[i], opts.prefix, opts.suffix)
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 == 'full'):
_tester.set_double_strategy(_utils.DOUBLE_STRATEGY.FULL)
elif(opts.double_strategy == 'partial'):
_tester.set_double_strategy(_utils.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)
func _print_gutconfigs(values):
var header = """Here is a sample of a full .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 (the resolved values where default < .gutconfig < command line)."""
print("\n", header.replace("\n", ' '), "\n\n")
var resolved = values
# remove 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.")
var text = JSON.print(resolved)
print(text.replace(',', ",\n"))
for key in resolved:
resolved[key] = null
print("\n\nAnd here's an empty config for you fill in what you want.")
text = JSON.print(resolved)
print(text.replace(',', ",\n"))
# parse options and run Gut
func _init():
var opt_resolver = OptionResolver.new()
opt_resolver.set_base_opts(options)
print("\n\n", ' --- Gut ---')
var o = setup_options()
var all_options_valid = o.parse()
extract_command_line_options(o, opt_resolver.cmd_opts)
var load_result = \
load_options_from_config_file(opt_resolver.get_value('config_file'), opt_resolver.config_opts)
if(load_result == -1): # -1 indicates json parse error
quit()
else:
if(!all_options_valid):
quit()
elif(o.get_value('-gh')):
var v_info = Engine.get_version_info()
print(str('Godot version: ', v_info.major, '.', v_info.minor, '.', v_info.patch))
print(str('GUT version: ', Gut.new().get_version()))
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 < .gutconfig < cmd line).' + "\n")
print(opt_resolver.to_s_verbose())
quit()
elif(o.get_value('-gprint_gutconfig_sample')):
_print_gutconfigs(opt_resolver.get_resolved_values())
quit()
else:
apply_options(opt_resolver.get_resolved_values())
_tester.test_scripts(!_run_single)
# exit if option is set.
func _on_tests_finished(should_exit, should_exit_on_success):
if(_tester.get_fail_count()):
OS.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(should_exit or (should_exit_on_success and _tester.get_fail_count() == 0)):
quit()
else:
print("Tests finished, exit manually")

View File

@ -1,12 +0,0 @@
tool
extends EditorPlugin
func _enter_tree():
# Initialization of the plugin goes here
# Add the new type with a name, a parent type, a script and an icon
add_custom_type("Gut", "Control", preload("gut.gd"), preload("icon.png"))
func _exit_tree():
# Clean-up of the plugin goes here
# Always remember to remove it from the engine when deactivated
remove_custom_type("Gut")

View File

@ -1,35 +0,0 @@
# ------------------------------------------------------------------------------
# This script is the base for custom scripts to be used in pre and post
# run hooks.
# ------------------------------------------------------------------------------
# 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
# the exit code to be used by gut_cmdln. See set method.
var _exit_code = null
var _should_abort = false
# Virtual method that will be called by GUT after instantiating
# this script.
func run():
pass
# 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
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

View File

@ -1,34 +0,0 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-91b084043b8aaf2f1c906e7b9fa92969.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/icon.png"
dest_files=[ "res://.import/icon.png-91b084043b8aaf2f1c906e7b9fa92969.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

View File

@ -1,105 +0,0 @@
extends Node2D
var _gut = null
var types = {
warn = 'WARNING',
error = 'ERROR',
info = 'INFO',
debug = 'DEBUG',
deprecated = 'DEPRECATED'
}
var _logs = {
types.warn: [],
types.error: [],
types.info: [],
types.debug: [],
types.deprecated: []
}
var _suppress_output = false
func _gut_log_level_for_type(log_type):
if(log_type == types.warn or log_type == types.error or log_type == types.deprecated):
return 0
else:
return 2
func _log(type, text):
_logs[type].append(text)
var formatted = str('[', type, '] ', text)
if(!_suppress_output):
if(_gut):
# this will keep the text indented under test for readability
_gut.p(formatted, _gut_log_level_for_type(type))
# IDEA! We could store the current script and test that generated
# this output, which could be useful later if we printed out a summary.
else:
print(formatted)
return formatted
# ---------------
# 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):
var count = 0
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 warn(text):
return _log(types.warn, text)
func error(text):
return _log(types.error, text)
func info(text):
return _log(types.info, text)
func debug(text):
return _log(types.debug, text)
# supply some text or the name of the deprecated method and the replacement.
func deprecated(text, alt_method=null):
var msg = text
if(alt_method):
msg = str('The method ', text, ' is deprecated, use ', alt_method , ' instead.')
return _log(types.deprecated, msg)
# ---------------
# Misc
# ---------------
func get_gut():
return _gut
func set_gut(gut):
_gut = gut
func clear():
for key in _logs:
_logs[key].clear()

View File

@ -1,200 +0,0 @@
# This class will generate method declaration lines based on method meta
# data. It will create defaults that match the method data.
#
# --------------------
# function meta data
# --------------------
# name:
# flags:
# args: [{
# (class_name:),
# (hint:0),
# (hint_string:),
# (name:),
# (type:4),
# (usage:7)
# }]
# default_args []
var _utils = load('res://addons/gut/utils.gd').new()
var _lgr = _utils.get_logger()
const PARAM_PREFIX = 'p_'
# ------------------------------------------------------
# _supported_defaults
#
# This array contains all the data types that are supported for default values.
# If a value is supported it will contain either an empty string or a prefix
# that should be used when setting the parameter default value.
# For example int, real, bool do not need anything func(p1=1, p2=2.2, p3=false)
# 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_REAL = 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_VECTOR2_ARRAY = 24 — Variable is of type PoolVector2Array.
# TYPE_TRANSFORM2D = 8 — Variable is of type Transform2D.
# TYPE_PLANE = 9 — Variable is of type Plane.
# TYPE_QUAT = 10 — Variable is of type Quat.
# TYPE_AABB = 11 — Variable is of type AABB.
# TYPE_BASIS = 12 — Variable is of type Basis.
# TYPE_TRANSFORM = 13 — Variable is of type Transform.
# TYPE_NODE_PATH = 15 — Variable is of type NodePath.
# TYPE_RID = 16 — Variable is of type RID.
# TYPE_RAW_ARRAY = 20 — Variable is of type PoolByteArray.
# TYPE_INT_ARRAY = 21 — Variable is of type PoolIntArray.
# TYPE_REAL_ARRAY = 22 — Variable is of type PoolRealArray.
# TYPE_STRING_ARRAY = 23 — Variable is of type PoolStringArray.
# TYPE_VECTOR3_ARRAY = 25 — Variable is of type PoolVector3Array.
# TYPE_COLOR_ARRAY = 26 — Variable is of type PoolColorArray.
# 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_REAL] = ''
_supported_defaults[TYPE_OBJECT] = ''
_supported_defaults[TYPE_ARRAY] = ''
_supported_defaults[TYPE_STRING] = ''
_supported_defaults[TYPE_DICTIONARY] = ''
_supported_defaults[TYPE_VECTOR2_ARRAY] = ''
# 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'
# ###############
# Private
# ###############
func _is_supported_default(type_flag):
return type_flag >= 0 and type_flag < _supported_defaults.size() and [type_flag] != null
# Creates a list of parameters with defaults of null unless a default value is
# found in the metadata. If a default is found in the meta then it is used if
# it is one we know how support.
#
# If a default is found that we don't know how to handle then this method will
# return null.
func _get_arg_text(method_meta):
var text = ''
var args = method_meta.args
var defaults = []
var has_unsupported_defaults = false
# fill up the defaults with null defaults for everything that doesn't have
# a default in the meta data. default_args is an array of default values
# for the last n parameters where n is the size of default_args so we only
# add nulls for everything up to the first parameter with a default.
for i in range(args.size() - method_meta.default_args.size()):
defaults.append('null')
# Add meta-data defaults.
for i in range(method_meta.default_args.size()):
var t = args[defaults.size()]['type']
var value = ''
if(_is_supported_default(t)):
# strings are special, they need quotes around the value
if(t == TYPE_STRING):
value = str("'", str(method_meta.default_args[i]), "'")
# Colors need the parens but things like Vector2 and Rect2 don't
elif(t == TYPE_COLOR):
value = str(_supported_defaults[t], '(', str(method_meta.default_args[i]), ')')
elif(t == TYPE_OBJECT):
if(str(method_meta.default_args[i]) == "[Object:null]"):
value = str(_supported_defaults[t], 'null')
else:
value = str(_supported_defaults[t], str(method_meta.default_args[i]).to_lower())
# 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:
value = str(_supported_defaults[t], str(method_meta.default_args[i]).to_lower())
else:
_lgr.warn(str(
'Unsupported default param type: ',method_meta.name, '-', args[defaults.size()].name, ' ', t, ' = ', method_meta.default_args[i]))
value = str('unsupported=',t)
has_unsupported_defaults = true
defaults.append(value)
# construct the string of parameters
for i in range(args.size()):
text += str(PARAM_PREFIX, args[i].name, '=', defaults[i])
if(i != args.size() -1):
text += ', '
# if we don't know how to make a default then we have to return null b/c
# it will cause a runtime error and it's one thing we could return to let
# callers know it didn't work.
if(has_unsupported_defaults):
text = null
return text
# ###############
# Public
# ###############
# 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_decleration_text(meta):
var param_text = _get_arg_text(meta)
var text = null
if(param_text != null):
text = str('func ', meta.name, '(', param_text, '):')
return text
# creates a call to the function in meta in the super's class.
func get_super_call_text(meta):
var params = ''
var all_supported = true
for i in range(meta.args.size()):
params += PARAM_PREFIX + meta.args[i].name
if(meta.args.size() > 1 and i != meta.args.size() -1):
params += ', '
return str('.', meta.name, '(', params, ')')
func get_spy_call_parameters_text(meta):
var called_with = 'null'
if(meta.args.size() > 0):
called_with = '['
for i in range(meta.args.size()):
called_with += str(PARAM_PREFIX, meta.args[i].name)
if(i < meta.args.size() - 1):
called_with += ', '
called_with += ']'
return called_with
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger

View File

@ -1,38 +0,0 @@
# ------------------------------------------------------------------------------
# This datastructure represents a simple one-to-many relationship. It manages
# a dictionary of value/array pairs. It ignores duplicates of both the "one"
# and the "many".
# ------------------------------------------------------------------------------
var _items = {}
# return the size of _items or the size of an element in _items if "one" was
# specified.
func size(one=null):
var to_return = 0
if(one == null):
to_return = _items.size()
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)):
_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)):
to_return = _items[one].has(many_item)
return to_return
func to_s():
var to_return = ''
for key in _items:
to_return += str(key, ": ", _items[key], "\n")
return to_return

View File

@ -1,250 +0,0 @@
################################################################################
#(G)odot (U)nit (T)est class
#
################################################################################
#The MIT License (MIT)
#=====================
#
#Copyright (c) 2019 Tom "Butch" Wesley
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
#
################################################################################
# Description
# -----------
# Command line interface for the GUT unit testing tool. Allows you to run tests
# from the command line instead of running a scene. Place this script along with
# gut.gd into your scripts directory at the root of your project. Once there you
# can run this script (from the root of your project) using the following command:
# godot -s -d test/gut/gut_cmdln.gd
#
# See the readme for a list of options and examples. You can also use the -gh
# option to get more information about how to use the command line interface.
#
# Version 6.8.1
################################################################################
#-------------------------------------------------------------------------------
# Parses the command line arguments supplied into an array that can then be
# examined and parsed based on how the gut options work.
#-------------------------------------------------------------------------------
class CmdLineParser:
var _used_options = []
# an array of arrays. Each element in this array will contain an option
# name and if that option contains a value then it will have a sedond
# element. For example:
# [[-gselect, test.gd], [-gexit]]
var _opts = []
func _init():
for i in range(OS.get_cmdline_args().size()):
var opt_val = OS.get_cmdline_args()[i].split('=')
_opts.append(opt_val)
# Parse out multiple comma delimited values from a command line
# option. Values are separated from option name with "=" and
# additional values are comma separated.
func _parse_array_value(full_option):
var value = _parse_option_value(full_option)
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):
return full_option[1]
else:
return null
# Search _opts for an element that starts with the option name
# specified.
func find_option(name):
var found = false
var idx = 0
while(idx < _opts.size() and !found):
if(_opts[idx][0] == name):
found = true
else:
idx += 1
if(found):
return idx
else:
return -1
func get_array_value(option):
_used_options.append(option)
var to_return = []
var opt_loc = find_option(option)
if(opt_loc != -1):
to_return = _parse_array_value(_opts[opt_loc])
_opts.remove(opt_loc)
return to_return
# returns the value of an option if it was specified, null otherwise. This
# used to return the default but that became problemnatic when trying to
# punch through the different places where values could be specified.
func get_value(option):
_used_options.append(option)
var to_return = null
var opt_loc = find_option(option)
if(opt_loc != -1):
to_return = _parse_option_value(_opts[opt_loc])
_opts.remove(opt_loc)
return to_return
# returns true if it finds the option, false if not.
func was_specified(option):
_used_options.append(option)
return find_option(option) != -1
# Returns any unused command line options. I found that only the -s and
# script name come through from godot, all other options that godot uses
# are not sent through OS.get_cmdline_args().
#
# This is a onetime thing b/c i kill all items in _used_options
func get_unused_options():
var to_return = []
for i in range(_opts.size()):
to_return.append(_opts[i][0])
var script_option = to_return.find('-s')
if script_option != -1:
to_return.remove(script_option + 1)
to_return.remove(script_option)
while(_used_options.size() > 0):
var index = to_return.find(_used_options[0].split("=")[0])
if(index != -1):
to_return.remove(index)
_used_options.remove(0)
return to_return
#-------------------------------------------------------------------------------
# Simple class to hold a command line option
#-------------------------------------------------------------------------------
class Option:
var value = null
var option_name = ''
var default = null
var description = ''
func _init(name, default_value, desc=''):
option_name = name
default = default_value
description = desc
value = null#default_value
func pad(value, size, pad_with=' '):
var to_return = value
for i in range(value.length(), size):
to_return += pad_with
return to_return
func to_s(min_space=0):
var subbed_desc = description
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
# the command line and make it easily accessible.
#-------------------------------------------------------------------------------
var options = []
var _opts = []
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):
found = true
else:
idx += 1
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):
longest = options[i].option_name.length()
print('---------------------------------------------------------')
print(_banner)
print("\nOptions\n-------")
for i in range(options.size()):
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))
func parse():
var parser = CmdLineParser.new()
for i in range(options.size()):
var t = typeof(options[i].default)
# only set values that were specified at the command line so that
# we can punch through default and config values correctly later.
# 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):
options[i].value = int(parser.get_value(options[i].option_name))
elif(t == TYPE_STRING):
options[i].value = parser.get_value(options[i].option_name)
elif(t == TYPE_ARRAY):
options[i].value = parser.get_array_value(options[i].option_name)
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')
else:
print(options[i].option_name + ' cannot be processed, it has unknown datatype:' + str(t))
var unused = parser.get_unused_options()
if(unused.size() > 0):
print("Unrecognized options: ", unused)
return false
return true

View File

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

View File

@ -1,166 +0,0 @@
################################################################################
#The MIT License (MIT)
#=====================
#
#Copyright (c) 2019 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.
#
################################################################################
# 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_*_'
# 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
# each time the signal was emitted.
#
# For example:
# _watched_signals => {
# ref1 => {
# 'signal1' => [[], [], []],
# 'signal2' => [[p1, p2]],
# 'signal3' => [[p1]]
# },
# ref2 => {
# 'some_signal' => [],
# 'other_signal' => [[p1, p2, p3], [p1, p2, p3], [p1, p2, p3]]
# }
# }
#
# In this sample:
# - signal1 on the ref1 object was emitted 3 times and each time, zero
# 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').new()
func _add_watched_signal(obj, name):
# SHORTCIRCUIT - ignore dupes
if(_watched_signals.has(obj) and _watched_signals[obj].has(name)):
return
if(!_watched_signals.has(obj)):
_watched_signals[obj] = {name:[]}
else:
_watched_signals[obj][name] = []
obj.connect(name, self, '_on_watched_signal', [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
# connected via watch_signal. I chose 9 since you can only specify up to 9
# parameters when dynamically calling a method via call (per the Godot
# documentation, i.e. some_object.call('some_method', 1, 2, 3...)).
#
# 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):
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):
args.remove(idx)
idx -= 1
# retrieve object and signal name from the array and remove them. These
# will always be at the end since they are added when the connect happens.
var signal_name = args[args.size() -1]
args.pop_back()
var object = args[args.size() -1]
args.pop_back()
_watched_signals[object][signal_name].append(args)
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):
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'])
func watch_signal(object, signal_name):
var did = false
if(does_object_have_signal(object, signal_name)):
_add_watched_signal(object, signal_name)
did = true
return did
func get_emit_count(object, signal_name):
var to_return = -1
if(is_watching(object, signal_name)):
to_return = _watched_signals[object][signal_name].size()
return to_return
func did_emit(object, signal_name):
var did = false
if(is_watching(object, signal_name)):
did = get_emit_count(object, 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):
var params = null
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
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:
for signal_name in _watched_signals[obj]:
if(_utils.is_not_freed(obj)):
obj.disconnect(signal_name, 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)):
for signal_name in _watched_signals[obj]:
if(_watched_signals[obj][signal_name].size() > 0):
emitted.append(signal_name)
return emitted

Binary file not shown.

View File

@ -1,96 +0,0 @@
# {
# instance_id_or_path1:{
# method1:[ [p1, p2], [p1, p2] ],
# method2:[ [p1, p2], [p1, p2] ]
# },
# instance_id_or_path1:{
# method1:[ [p1, p2], [p1, p2] ],
# method2:[ [p1, p2], [p1, p2] ]
# },
# }
var _calls = {}
var _utils = load('res://addons/gut/utils.gd').new()
var _lgr = _utils.get_logger()
func _get_params_as_string(params):
var to_return = ''
if(params == null):
return ''
for i in range(params.size()):
if(params[i] == null):
to_return += 'null'
else:
if(typeof(params[i]) == TYPE_STRING):
to_return += str('"', params[i], '"')
else:
to_return += str(params[i])
if(i != params.size() -1):
to_return += ', '
return to_return
func add_call(variant, method_name, parameters=null):
if(!_calls.has(variant)):
_calls[variant] = {}
if(!_calls[variant].has(method_name)):
_calls[variant][method_name] = []
_calls[variant][method_name].append(parameters)
func was_called(variant, method_name, parameters=null):
var to_return = false
if(_calls.has(variant) and _calls[variant].has(method_name)):
if(parameters):
to_return = _calls[variant][method_name].has(parameters)
else:
to_return = true
return to_return
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)):
var call_size = _calls[variant][method_name].size()
if(index == -1):
# get the most recent call by default
get_index = call_size -1
else:
get_index = index
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))
return to_return
func call_count(instance, method_name, parameters=null):
var to_return = 0
if(was_called(instance, method_name)):
if(parameters):
for i in range(_calls[instance][method_name].size()):
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)):
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")
return to_return
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger

View File

@ -1,43 +0,0 @@
var return_val = null
var stub_target = null
var target_subpath = null
var parameters = null
var stub_method = null
var call_super = false
const NOT_SET = '|_1_this_is_not_set_1_|'
func _init(target=null, method=null, subpath=null):
stub_target = target
stub_method = method
target_subpath = subpath
func to_return(val):
return_val = val
call_super = false
return self
func to_do_nothing():
return to_return(null)
func to_call_super():
call_super = true
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):
parameters.remove(idx)
else:
idx += 1
return self
func to_s():
var base_string = str(stub_target, '[', target_subpath, '].', stub_method)
if(call_super):
base_string += " to call SUPER"
else:
base_string += str(' with (', parameters, ') = ', return_val)
return base_string

View File

@ -1,162 +0,0 @@
# {
# inst_id_or_path1:{
# method_name1: [StubParams, StubParams],
# method_name2: [StubParams, StubParams]
# },
# inst_id_or_path2:{
# method_name1: [StubParams, StubParams],
# method_name2: [StubParams, StubParams]
# }
# }
var returns = {}
var _utils = load('res://addons/gut/utils.gd').new()
var _lgr = _utils.get_logger()
func _is_instance(obj):
return typeof(obj) == TYPE_OBJECT and !obj.has_method('new')
func _make_key_from_metadata(doubled):
var to_return = doubled.__gut_metadata_.path
if(doubled.__gut_metadata_.subpath != ''):
to_return += str('-', doubled.__gut_metadata_.subpath)
return to_return
# 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
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(_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(_is_instance(obj)):
key = obj
if(!returns.has(key)):
returns[key] = {}
if(!returns[key].has(method)):
returns[key][method] = []
return key
# ##############
# Public
# ##############
# TODO: This method is only used in tests and should be refactored out. It
# does not support inner classes and isn't helpful.
func set_return(obj, method, value, parameters=null):
var key = _add_obj_method(obj, method)
var sp = _utils.StubParams.new(key, method)
sp.parameters = parameters
sp.return_val = value
returns[key][method].append(sp)
func add_stub(stub_params):
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)
# 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):
var key = _make_key_from_variant(obj)
var to_return = null
if(_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(returns.has(key) and returns[key].has(method)):
var param_idx = -1
var null_idx = -1
for i in range(returns[key][method].size()):
if(returns[key][method][i].parameters == parameters):
param_idx = i
if(returns[key][method][i].parameters == null):
null_idx = i
# We have matching parameter values so return the stub value for that
if(param_idx != -1):
to_return = returns[key][method][param_idx]
# We found a case where the parameters were not specified so return
# parameters for that
elif(null_idx != -1):
to_return = returns[key][method][null_idx]
else:
_lgr.warn(str('Call to [', method, '] was not stubbed for the supplied parameters ', parameters, '. Null was returned.'))
return to_return
# Gets a stubbed return value for the object and method passed in. If the
# instance was stubbed it will use that, otherwise it will use the path and
# subpath of the object to try to find a value.
#
# It will also use the optional list of parameter values to find a value. If
# the object was stubbed with no parameters than any parameters will match.
# If it was stubbed with specific parameter values then it will try to match.
# If the parameters do not match BUT there was also an empty parameter list stub
# then it will return those.
# If it cannot find anything that matches then null is returned.for
#
# Parameters
# 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):
var stub_info = _find_stub(obj, method, parameters)
if(stub_info != null):
return stub_info.return_val
else:
return null
func should_call_super(obj, method, parameters=null):
var stub_info = _find_stub(obj, method, parameters)
if(stub_info != null):
return stub_info.call_super
else:
# 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 + '::' + str(obj))
return false
func clear():
returns.clear()
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger
func to_s():
var text = ''
for thing in returns:
text += str(thing) + "\n"
for method in returns[thing]:
text += str("\t", method, "\n")
for i in range(returns[thing][method].size()):
text += "\t\t" + returns[thing][method][i].to_s() + "\n"
return text

View File

@ -1,153 +0,0 @@
# ------------------------------------------------------------------------------
# Contains all the results of a single test. Allows for multiple asserts results
# and pending calls.
# ------------------------------------------------------------------------------
class Test:
var pass_texts = []
var fail_texts = []
var pending_texts = []
func to_s():
var pad = ' '
var to_return = ''
for i in range(fail_texts.size()):
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")
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 _tests = {}
var _test_order = []
func _init(script_name):
name = script_name
func get_pass_count():
var count = 0
for key in _tests:
count += _tests[key].pass_texts.size()
return count
func get_fail_count():
var count = 0
for key in _tests:
count += _tests[key].fail_texts.size()
return count
func get_pending_count():
var count = 0
for key in _tests:
count += _tests[key].pending_texts.size()
return count
func get_test_obj(name):
if(!_tests.has(name)):
_tests[name] = Test.new()
_test_order.append(name)
return _tests[name]
func add_pass(test_name, reason):
var t = get_test_obj(test_name)
t.pass_texts.append(reason)
func add_fail(test_name, reason):
var t = get_test_obj(test_name)
t.fail_texts.append(reason)
func add_pending(test_name, reason):
var t = get_test_obj(test_name)
t.pending_texts.append(reason)
# ------------------------------------------------------------------------------
# Summary Class
#
# This class holds the results of all the test scripts and Inner Classes that
# were run.
# -------------------------------------------d-----------------------------------
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):
get_current_script().get_test_obj(test_name)
func add_pass(test_name, reason = ''):
get_current_script().add_pass(test_name, reason)
func add_fail(test_name, reason = ''):
get_current_script().add_fail(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 count = 0
var unique_scripts = {}
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()
func get_totals():
var totals = {
passing = 0,
pending = 0,
failing = 0,
tests = 0,
scripts = 0
}
for s in range(_scripts.size()):
totals.passing += _scripts[s].get_pass_count()
totals.pending += _scripts[s].get_pending_count()
totals.failing += _scripts[s].get_fail_count()
totals.tests += _scripts[s]._test_order.size()
totals.scripts = get_non_inner_class_script_count()
return totals
func get_summary_text():
var _totals = get_totals()
var to_return = ''
for s in range(_scripts.size()):
if(_scripts[s].get_fail_count() > 0 or _scripts[s].get_pending_count() > 0):
to_return += _scripts[s].name + "\n"
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):
to_return += str(' - ', tname, "\n", test.to_s())
var header = "*** Totals ***\n"
header += str(' scripts: ', get_non_inner_class_script_count(), "\n")
header += str(' tests: ', _totals.tests, "\n")
header += str(' passing asserts: ', _totals.passing, "\n")
header += str(' failing asserts: ',_totals.failing, "\n")
header += str(' pending: ', _totals.pending, "\n")
return to_return + "\n" + header

File diff suppressed because it is too large Load Diff

View File

@ -1,241 +0,0 @@
# ------------------------------------------------------------------------------
# Used to keep track of info about each test ran.
# ------------------------------------------------------------------------------
class Test:
# indicator if it passed or not. defaults to true since it takes only
# one failure to make it not pass. _fail in gut will set this.
var passed = true
# the name of the function
var name = ""
# flag to know if the name has been printed yet.
var has_printed_name = false
# the line number the test is on
var line_number = -1
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
class TestScript:
var inner_class_name = null
var tests = []
var path = null
var _utils = null
var _lgr = null
func _init(utils=null, logger=null):
_utils = utils
_lgr = logger
func to_s():
var to_return = path
if(inner_class_name != null):
to_return += str('.', inner_class_name)
to_return += "\n"
for i in range(tests.size()):
to_return += str(' ', tests[i].name, "\n")
return to_return
func get_new():
var TheScript = load(path)
var inst = null
if(inner_class_name != null):
inst = TheScript.get(inner_class_name).new()
else:
inst = TheScript.new()
return inst
func get_full_name():
var to_return = path
if(inner_class_name != null):
to_return += '.' + inner_class_name
return to_return
func get_filename():
return path.get_file()
func has_inner_class():
return inner_class_name != null
func export_to(config_file, section):
config_file.set_value(section, 'path', path)
config_file.set_value(section, 'inner_class', inner_class_name)
var names = []
for i in range(tests.size()):
names.append(tests[i].name)
config_file.set_value(section, 'tests', names)
func _remap_path(path):
var to_return = path
if(!_utils.file_exists(path)):
_lgr.debug('Checking for remap for: ' + path)
var remap_path = path.get_basename() + '.gd.remap'
if(_utils.file_exists(remap_path)):
var cf = ConfigFile.new()
cf.load(remap_path)
to_return = cf.get_value('remap', 'path')
else:
_lgr.warn('Could not find remap file ' + remap_path)
return to_return
func import_from(config_file, section):
path = config_file.get_value(section, 'path')
path = _remap_path(path)
var test_names = config_file.get_value(section, 'tests')
for i in range(test_names.size()):
var t = Test.new()
t.name = test_names[i]
tests.append(t)
# Null is an acceptable value, but you can't pass null as a default to
# get_value since it thinks you didn't send a default...then it spits
# out red text. This works around that.
var inner_name = config_file.get_value(section, 'inner_class', 'Placeholder')
if(inner_name != 'Placeholder'):
inner_class_name = inner_name
else: # just being explicit
inner_class_name = null
# ------------------------------------------------------------------------------
# start test_collector, I don't think I like the name.
# ------------------------------------------------------------------------------
var scripts = []
var _test_prefix = 'test_'
var _test_class_prefix = 'Test'
var _utils = load('res://addons/gut/utils.gd').new()
var _lgr = _utils.get_logger()
func _parse_script(script):
var file = File.new()
var line = ""
var line_count = 0
var inner_classes = []
var scripts_found = []
file.open(script.path, 1)
while(!file.eof_reached()):
line_count += 1
line = file.get_line()
#Add a test
if(line.begins_with("func " + _test_prefix)):
var from = line.find(_test_prefix)
var line_len = line.find("(") - from
var new_test = Test.new()
new_test.name = line.substr(from, line_len)
new_test.line_number = line_count
script.tests.append(new_test)
if(line.begins_with('class ')):
var iclass_name = line.replace('class ', '')
iclass_name = iclass_name.replace(':', '')
if(iclass_name.begins_with(_test_class_prefix)):
inner_classes.append(iclass_name)
scripts_found.append(script.path)
for i in range(inner_classes.size()):
var ts = TestScript.new(_utils, _lgr)
ts.path = script.path
ts.inner_class_name = inner_classes[i]
if(_parse_inner_class_tests(ts)):
scripts.append(ts)
scripts_found.append(script.path + '[' + inner_classes[i] +']')
file.close()
return scripts_found
func _parse_inner_class_tests(script):
var inst = script.get_new()
if(!inst is _utils.Test):
_lgr.warn('Ignoring ' + script.inner_class_name + ' because it starts with "' + _test_class_prefix + '" but does not extend addons/gut/test.gd')
return false
var methods = inst.get_method_list()
for i in range(methods.size()):
var name = methods[i]['name']
if(name.begins_with(_test_prefix) and methods[i]['flags'] == 65):
var t = Test.new()
t.name = name
script.tests.append(t)
return true
# -----------------
# Public
# -----------------
func add_script(path):
# SHORTCIRCUIT
if(has_script(path)):
return []
var f = File.new()
# SHORTCIRCUIT
if(!f.file_exists(path)):
_lgr.error('Could not find script: ' + path)
return
var ts = TestScript.new(_utils, _lgr)
ts.path = path
scripts.append(ts)
return _parse_script(ts)
func to_s():
var to_return = ''
for i in range(scripts.size()):
to_return += scripts[i].to_s() + "\n"
return to_return
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger
func get_test_prefix():
return _test_prefix
func set_test_prefix(test_prefix):
_test_prefix = test_prefix
func get_test_class_prefix():
return _test_class_prefix
func set_test_class_prefix(test_class_prefix):
_test_class_prefix = test_class_prefix
func clear():
scripts.clear()
func has_script(path):
var found = false
var idx = 0
while(idx < scripts.size() and !found):
if(scripts[idx].path == path):
found = true
else:
idx += 1
return found
func export_tests(path):
var success = true
var f = ConfigFile.new()
for i in range(scripts.size()):
scripts[i].export_to(f, str('TestScript-', i))
var result = f.save(path)
if(result != OK):
_lgr.error(str('Could not save exported tests to [', path, ']. Error code: ', result))
success = false
return success
func import_tests(path):
var success = false
var f = ConfigFile.new()
var result = f.load(path)
if(result != OK):
_lgr.error(str('Could not load exported tests from [', path, ']. Error code: ', result))
else:
var sections = f.get_sections()
for key in sections:
var ts = TestScript.new(_utils, _lgr)
ts.import_from(f, key)
scripts.append(ts)
success = true
return success

View File

@ -1,43 +0,0 @@
var things = {}
func get_unique_count():
return things.size()
func add(thing):
if(things.has(thing)):
things[thing] += 1
else:
things[thing] = 1
func has(thing):
return things.has(thing)
func get(thing):
var to_return = 0
if(things.has(thing)):
to_return = things[thing]
return to_return
func sum():
var count = 0
for key in things:
count += things[key]
return count
func to_s():
var to_return = ""
for key in things:
to_return += str(key, ": ", things[key], "\n")
to_return += str("sum: ", sum())
return to_return
func get_max_count():
var max_val = null
for key in things:
if(max_val == null or things[key] > max_val):
max_val = things[key]
return max_val
func add_array_items(array):
for i in range(array.size()):
add(array[i])

View File

@ -1,122 +0,0 @@
var _Logger = load('res://addons/gut/logger.gd') # everything should use get_logger
var Doubler = load('res://addons/gut/doubler.gd')
var HookScript = load('res://addons/gut/hook_script.gd')
var MethodMaker = load('res://addons/gut/method_maker.gd')
var Spy = load('res://addons/gut/spy.gd')
var Stubber = load('res://addons/gut/stubber.gd')
var StubParams = load('res://addons/gut/stub_params.gd')
var Summary = load('res://addons/gut/summary.gd')
var Test = load('res://addons/gut/test.gd')
var TestCollector = load('res://addons/gut/test_collector.gd')
var ThingCounter = load('res://addons/gut/thing_counter.gd')
var OneToMany = load('res://addons/gut/one_to_many.gd')
const GUT_METADATA = '__gut_metadata_'
enum DOUBLE_STRATEGY{
FULL,
PARTIAL
}
var _file_checker = File.new()
func is_version_30():
var info = Engine.get_version_info()
return info.major == 3 and info.minor == 0
func is_version_31():
var info = Engine.get_version_info()
return info.major == 3 and info.minor == 1
# ------------------------------------------------------------------------------
# Everything should get a logger through this.
#
# Eventually I want to make this get a single instance of a logger but I'm not
# sure how to do that without everything having to be in the tree which I
# DO NOT want to to do. I'm thinking of writings some instance ids to a file
# and loading them in the _init for this.
# ------------------------------------------------------------------------------
func get_logger():
return _Logger.new()
# ------------------------------------------------------------------------------
# Returns an array created by splitting the string by the delimiter
# ------------------------------------------------------------------------------
func split_string(to_split, delim):
var to_return = []
var loc = to_split.find(delim)
while(loc != -1):
to_return.append(to_split.substr(0, loc))
to_split = to_split.substr(loc + 1, to_split.length() - loc)
loc = to_split.find(delim)
to_return.append(to_split)
return to_return
# ------------------------------------------------------------------------------
# Returns a string containing all the elements in the array separated by delim
# ------------------------------------------------------------------------------
func join_array(a, delim):
var to_return = ''
for i in range(a.size()):
to_return += str(a[i])
if(i != a.size() -1):
to_return += str(delim)
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
# ------------------------------------------------------------------------------
# returns true if the object has been freed, false if not
#
# From what i've read, the weakref approach should work. It seems to work most
# of the time but sometimes it does not catch it. The str comparison seems to
# fill in the gaps. I've not seen any errors after adding that check.
# ------------------------------------------------------------------------------
func is_freed(obj):
var wr = weakref(obj)
return !(wr.get_ref() and str(obj) != '[Deleted Object]')
func is_not_freed(obj):
return !is_freed(obj)
func is_double(obj):
return obj.get(GUT_METADATA) != null
func extract_property_from_array(source, property):
var to_return = []
for i in (source.size()):
to_return.append(source[i].get(property))
return to_return
func file_exists(path):
return _file_checker.file_exists(path)
func write_file(path, content):
var f = File.new()
f.open(path, f.WRITE)
f.store_string(content)
f.close()
func is_null_or_empty(text):
return text == null or text == ''
func get_native_class_name(thing):
var to_return = null
if(is_native_class(thing)):
to_return = thing.new().get_class()
return to_return
func is_native_class(thing):
var it_is = false
if(typeof(thing) == TYPE_OBJECT):
it_is = str(thing).begins_with("[GDScriptNativeClass:")
return it_is

View File

@ -5,9 +5,6 @@ itchio := "damantisshrimp/taqin"
edit:
godot --editor --quiet &
test:
godot --debug --script {{src_dir}}/addons/gut/gut_cmdln.gd
export-android:
mkdir -p {{build_dir}}/android
godot --export "Android" {{build_dir}}/android

View File

@ -40,10 +40,6 @@ window/size/height.mobile=1200
window/stretch/mode.mobile="2d"
window/stretch/aspect.mobile="expand"
[editor_plugins]
enabled=PoolStringArray( "gut" )
[input_devices]
pointing/emulate_touch_from_mouse=true

View File

@ -1,38 +0,0 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/gut/gut.gd" type="Script" id=1]
[node name="Gut" type="Control"]
self_modulate = Color( 1, 1, 1, 0 )
anchor_right = 1.0
anchor_bottom = 1.0
rect_min_size = Vector2( 740, 250 )
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
_select_script = ""
_tests_like = ""
_inner_class_name = ""
_run_on_load = false
_should_maximize = false
_should_print_to_console = true
_log_level = 1
_yield_between_tests = false
_disable_strict_datatype_checks = false
_test_prefix = "test_"
_file_prefix = "test_"
_file_extension = ".gd"
_inner_class_prefix = "Test"
_temp_directory = "user://gut_temp_directory"
_export_path = ""
_include_subdirectories = false
_directory1 = "res://test/unit"
_directory2 = ""
_directory3 = ""
_directory4 = ""
_directory5 = ""
_directory6 = ""
_double_strategy = 1
_pre_run_script = ""
_post_run_script = ""

View File

@ -1,27 +0,0 @@
extends "res://addons/gut/test.gd"
func before_each():
gut.p("ran setup", 2)
func after_each():
gut.p("ran teardown", 2)
func before_all():
gut.p("ran run setup", 2)
func after_all():
gut.p("ran run teardown", 2)
func test_assert_eq_number_not_equal():
assert_eq(1, 2, "Should fail. 1 != 2")
func test_assert_eq_number_equal():
assert_eq('asdf', 'asdf', "Should pass")
func test_assert_true_with_true():
assert_true(true, "Should pass, true is true")
func test_assert_true_with_false():
assert_true(false, "Should fail")
func test_something_else():
assert_true(false, "didn't work")