upgrade gut

This commit is contained in:
anyreso 2026-02-19 03:14:22 -05:00
parent 6e724f67fe
commit 82aeebfd35
201 changed files with 8547 additions and 3871 deletions

View file

@ -91,9 +91,11 @@ func _set_font(rtl, font_name, custom_name):
if(font_name == null):
rtl.remove_theme_font_override(custom_name)
else:
var dyn_font = FontFile.new()
dyn_font.load_dynamic_font('res://addons/gut/fonts/' + font_name + '.ttf')
rtl.add_theme_font_override(custom_name, dyn_font)
var font_path = 'res://addons/gut/fonts/' + font_name + '.ttf'
if(FileAccess.file_exists(font_path)):
var dyn_font = FontFile.new()
dyn_font.load_dynamic_font('res://addons/gut/fonts/' + font_name + '.ttf')
rtl.add_theme_font_override(custom_name, dyn_font)
func _set_all_fonts_in_rtl(rtl, base_name):

View file

@ -1 +1 @@
uid://bnydli77wadhj
uid://bw7tukh738kw1

View file

@ -1,6 +1,6 @@
[gd_scene load_steps=4 format=3 uid="uid://m28heqtswbuq"]
[ext_resource type="Script" path="res://addons/gut/GutScene.gd" id="1_b4m8y"]
[ext_resource type="Script" uid="uid://bw7tukh738kw1" path="res://addons/gut/GutScene.gd" id="1_b4m8y"]
[ext_resource type="PackedScene" uid="uid://duxblir3vu8x7" path="res://addons/gut/gui/NormalGui.tscn" id="2_j6ywb"]
[ext_resource type="PackedScene" uid="uid://cnqqdfsn80ise" path="res://addons/gut/gui/MinGui.tscn" id="3_3glw1"]

View file

@ -1 +1 @@
uid://d08awoq0h05xb
uid://x51wilphva3d

View file

@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bsm7wtt1gie4v"]
[ext_resource type="Script" path="res://addons/gut/UserFileViewer.gd" id="1"]
[ext_resource type="Script" uid="uid://x51wilphva3d" path="res://addons/gut/UserFileViewer.gd" id="1"]
[node name="UserFileViewer" type="Window"]
exclusive = true

View file

@ -30,28 +30,57 @@
# ##############################################################################
var _to_free = []
var _to_queue_free = []
var _ref_counted_doubles = []
var _all_instance_ids = []
func _add_instance_id(thing):
if(thing.has_method("get_instance_id")):
_all_instance_ids.append(thing.get_instance_id())
func add_free(thing):
if(typeof(thing) == TYPE_OBJECT):
_add_instance_id(thing)
if(!thing is RefCounted):
_to_free.append(thing)
elif(GutUtils.is_double(thing)):
_ref_counted_doubles.append(thing)
func add_queue_free(thing):
_to_queue_free.append(thing)
if(typeof(thing) == TYPE_OBJECT):
_add_instance_id(thing)
_to_queue_free.append(thing)
func get_queue_free_count():
return _to_queue_free.size()
func get_free_count():
return _to_free.size()
func free_all():
for i in range(_to_free.size()):
if(is_instance_valid(_to_free[i])):
_to_free[i].free()
for node in _to_free:
if(is_instance_valid(node)):
if(GutUtils.is_double(node)):
node.__gutdbl_done()
node.free()
_to_free.clear()
for i in range(_to_queue_free.size()):
if(is_instance_valid(_to_queue_free[i])):
_to_queue_free[i].queue_free()
_to_queue_free.clear()
for ref_dbl in _ref_counted_doubles:
ref_dbl.__gutdbl_done()
_ref_counted_doubles.clear()
_all_instance_ids.clear()
func has_instance_id(id):
return _all_instance_ids.has(id)

View file

@ -1 +1 @@
uid://bfrf3uq3hieco
uid://bxjfriqxgwe0r

View file

@ -1,60 +1,126 @@
extends Node
class AwaitLogger:
var _time_waited = 0.0
var logger = GutUtils.get_logger()
var waiting_on = "nothing"
var logged_initial_message = false
var wait_log_delay := 1.0
var disabled = false
func waited(x):
_time_waited += x
if(!logged_initial_message and _time_waited >= wait_log_delay):
log_it()
logged_initial_message = true
func reset():
_time_waited = 0.0
logged_initial_message = false
func log_it():
if(!disabled):
var msg = str("--- Awaiting ", waiting_on, " ---")
logger.wait_msg(msg)
signal timeout
signal wait_started
var await_logger = AwaitLogger.new()
var _wait_time := 0.0
var _wait_frames := 0
var _wait_process_frames := 0
var _wait_physics_frames := 0
var _signal_to_wait_on = null
var _predicate_function_waiting_to_be_true = null
var _predicate_method = null
var _waiting_for_predicate_to_be = null
var _predicate_time_between := 0.0
var _predicate_time_between_elpased := 0.0
var _elapsed_time := 0.0
var _elapsed_frames := 0
var _did_last_wait_timeout = false
var did_last_wait_timeout = false :
get: return _did_last_wait_timeout
set(val): push_error("Cannot set did_last_wait_timeout")
var _elapsed_time := 0.0
var _elapsed_frames := 0
func _ready() -> void:
get_tree().process_frame.connect(_on_tree_process_frame)
get_tree().physics_frame.connect(_on_tree_physics_frame)
func _on_tree_process_frame():
# Count frames here instead of in _process so that tree order never
# makes a difference and the count/signaling happens outside of
# _process being called.
if(_wait_process_frames > 0):
_elapsed_frames += 1
if(_elapsed_frames > _wait_process_frames):
_end_wait()
func _on_tree_physics_frame():
# Count frames here instead of in _physics_process so that tree order never
# makes a difference and the count/signaling happens outside of
# _physics_process being called.
if(_wait_physics_frames != 0):
_elapsed_frames += 1
if(_elapsed_frames > _wait_physics_frames):
_end_wait()
func _physics_process(delta):
if(is_waiting()):
await_logger.waited(delta)
if(_wait_time != 0.0):
_elapsed_time += delta
if(_elapsed_time >= _wait_time):
_end_wait()
if(_wait_frames != 0):
_elapsed_frames += 1
if(_elapsed_frames >= _wait_frames):
_end_wait()
if(_predicate_function_waiting_to_be_true != null):
if(_predicate_method != null):
_predicate_time_between_elpased += delta
if(_predicate_time_between_elpased >= _predicate_time_between):
_predicate_time_between_elpased = 0.0
var result = _predicate_function_waiting_to_be_true.call()
if(typeof(result) == TYPE_BOOL and result):
_end_wait()
var result = _predicate_method.call()
if(_waiting_for_predicate_to_be == false):
if(typeof(result) != TYPE_BOOL or result != true):
_end_wait()
else:
if(typeof(result) == TYPE_BOOL and result == _waiting_for_predicate_to_be):
_end_wait()
func _end_wait():
await_logger.reset()
# Check for time before checking for frames so that the extra frames added
# when waiting on a signal do not cause a false negative for timing out.
if(_wait_time > 0):
_did_last_wait_timeout = _elapsed_time >= _wait_time
elif(_wait_frames > 0):
_did_last_wait_timeout = _elapsed_frames >= _wait_frames
elif(_wait_physics_frames > 0):
_did_last_wait_timeout = _elapsed_frames >= _wait_physics_frames
elif(_wait_process_frames > 0):
_did_last_wait_timeout = _elapsed_frames >= _wait_process_frames
if(_signal_to_wait_on != null and _signal_to_wait_on.is_connected(_signal_callback)):
if(_signal_to_wait_on != null and \
is_instance_valid(_signal_to_wait_on.get_object()) and \
_signal_to_wait_on.is_connected(_signal_callback)):
_signal_to_wait_on.disconnect(_signal_callback)
_wait_process_frames = 0
_wait_time = 0.0
_wait_frames = 0
_wait_physics_frames = 0
_signal_to_wait_on = null
_predicate_function_waiting_to_be_true = null
_predicate_method = null
_elapsed_time = 0.0
_elapsed_frames = 0
timeout.emit()
@ -68,23 +134,34 @@ func _signal_callback(
_signal_to_wait_on.disconnect(_signal_callback)
# DO NOT _end_wait here. For other parts of the test to get the signal that
# was waited on, we have to wait for a couple more frames. For example, the
# was waited on, we have to wait for another frames. For example, the
# signal_watcher doesn't get the signal in time if we don't do this.
_wait_frames = 2
_wait_process_frames = 1
func wait_seconds(x):
func wait_seconds(x, msg=''):
await_logger.waiting_on = str(x, " seconds ", msg)
_did_last_wait_timeout = false
_wait_time = x
wait_started.emit()
func wait_frames(x):
func wait_process_frames(x, msg=''):
await_logger.waiting_on = str(x, " idle frames ", msg)
_did_last_wait_timeout = false
_wait_frames = x
_wait_process_frames = x
wait_started.emit()
func wait_for_signal(the_signal, max_time):
func wait_physics_frames(x, msg=''):
await_logger.waiting_on = str(x, " physics frames ", msg)
_did_last_wait_timeout = false
_wait_physics_frames = x
wait_started.emit()
func wait_for_signal(the_signal : Signal, max_time, msg=''):
await_logger.waiting_on = str("signal ", the_signal.get_name(), " or ", max_time, "s ", msg)
_did_last_wait_timeout = false
the_signal.connect(_signal_callback)
_signal_to_wait_on = the_signal
@ -92,14 +169,33 @@ func wait_for_signal(the_signal, max_time):
wait_started.emit()
func wait_until(predicate_function: Callable, max_time, time_between_calls:=0.0):
func wait_until(predicate_function: Callable, max_time, time_between_calls:=0.0, msg=''):
await_logger.waiting_on = str("callable to return TRUE or ", max_time, "s. ", msg)
_predicate_time_between = time_between_calls
_predicate_function_waiting_to_be_true = predicate_function
_predicate_method = predicate_function
_wait_time = max_time
_waiting_for_predicate_to_be = true
_predicate_time_between_elpased = 0.0
_did_last_wait_timeout = false
wait_started.emit()
func wait_while(predicate_function: Callable, max_time, time_between_calls:=0.0, msg=''):
await_logger.waiting_on = str("callable to return FALSE or ", max_time, "s. ", msg)
_predicate_time_between = time_between_calls
_predicate_method = predicate_function
_wait_time = max_time
_waiting_for_predicate_to_be = false
_predicate_time_between_elpased = 0.0
_did_last_wait_timeout = false
wait_started.emit()
func is_waiting():
return _wait_time != 0.0 || _wait_frames != 0
return _wait_time != 0.0 || \
_wait_physics_frames != 0 || \
_wait_process_frames != 0

View file

@ -1 +1 @@
uid://psark8vrstly
uid://ccu4ww35edtdi

View file

@ -1 +1 @@
uid://cfqihvbcwhtkg
uid://1pauyfnd1cre

View file

@ -105,6 +105,10 @@ Options whose values are lists/arrays can be specified multiple times:
-gdir c,d
-gdir e
# results in -gdir equaling [a, b, c, d, e]
To not use an empty value instead of a default value, specifiy the option with
an immediate "=":
-gconfig=
"""
opts.add_heading("Test Config:")
opts.add('-gdir', options.dirs, 'List of directories to search for test scripts in.')
@ -125,6 +129,8 @@ Options whose values are lists/arrays can be specified multiple times:
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 zero tests fail.')
opts.add('-gignore_pause', false, 'Ignores any calls to pause_before_teardown.')
opts.add('-gno_error_tracking', false, 'Disable error tracking.')
opts.add('-gfailure_error_types', options.failure_error_types, 'Error types that will cause tests to fail if the are encountered during the execution of a test. Default "[default]"')
opts.add_heading("Display Settings:")
opts.add('-glog', options.log_level, 'Log level [0-3]. Default [default]')
@ -138,6 +144,7 @@ Options whose values are lists/arrays can be specified multiple times:
opts.add('-gbackground_color', options.background_color, 'Background color as an html color, default "[default]"')
opts.add('-gfont_color',options.font_color, 'Font color as an html color, default "[default]"')
opts.add('-gpaint_after', options.paint_after, 'Delay before GUT will add a 1 frame pause to paint the screen/GUI. default [default]')
opts.add('-gwait_log_delay', options.wait_log_delay, 'Delay before GUT will print a message to indicate a test is awaiting one of the wait_* methods. Default [default]')
opts.add_heading("Result Export:")
opts.add('-gjunit_xml_file', options.junit_xml_file, 'Export results of run to this file in the Junit XML format.')
@ -148,16 +155,22 @@ Options whose values are lists/arrays can be specified multiple times:
opts.add('-gpo', false, 'Print option values from all sources and the value used.')
opts.add('-gprint_gutconfig_sample', false, 'Print out json that can be used to make a gutconfig file.')
# run as in editor, for shelling out purposes through Editor.
var o = opts.add('-graie', false, 'do not use')
o.show_in_help = false
return opts
# Parses options, applying them to the _tester or setting values
# in the options struct.
func extract_command_line_options(from, to):
to.compact_mode = from.get_value_or_null('-gcompact_mode')
to.config_file = from.get_value_or_null('-gconfig')
to.dirs = from.get_value_or_null('-gdir')
to.disable_colors = from.get_value_or_null('-gdisable_colors')
to.double_strategy = from.get_value_or_null('-gdouble_strategy')
to.errors_do_not_cause_failure = from.get_value_or_null('-gerrors_do_not_cause_failure')
to.hide_orphans = from.get_value_or_null('-ghide_orphans')
to.ignore_pause = from.get_value_or_null('-gignore_pause')
to.include_subdirs = from.get_value_or_null('-ginclude_subdirs')
to.inner_class = from.get_value_or_null('-ginner_class')
@ -170,22 +183,24 @@ func extract_command_line_options(from, to):
to.should_exit = from.get_value_or_null('-gexit')
to.should_exit_on_success = from.get_value_or_null('-gexit_on_success')
to.should_maximize = from.get_value_or_null('-gmaximize')
to.compact_mode = from.get_value_or_null('-gcompact_mode')
to.hide_orphans = from.get_value_or_null('-ghide_orphans')
to.suffix = from.get_value_or_null('-gsuffix')
to.errors_do_not_cause_failure = from.get_value_or_null('-gerrors_do_not_cause_failure')
to.tests = from.get_value_or_null('-gtest')
to.unit_test_name = from.get_value_or_null('-gunit_test_name')
to.wait_log_delay = from.get_value_or_null('-gwait_log_delay')
to.font_size = from.get_value_or_null('-gfont_size')
to.font_name = from.get_value_or_null('-gfont_name')
to.background_color = from.get_value_or_null('-gbackground_color')
to.font_color = from.get_value_or_null('-gfont_color')
to.font_name = from.get_value_or_null('-gfont_name')
to.font_size = from.get_value_or_null('-gfont_size')
to.paint_after = from.get_value_or_null('-gpaint_after')
to.junit_xml_file = from.get_value_or_null('-gjunit_xml_file')
to.junit_xml_timestamp = from.get_value_or_null('-gjunit_xml_timestamp')
to.failure_error_types = from.get_value_or_null('-gfailure_error_types')
to.no_error_tracking = from.get_value_or_null('-gno_error_tracking')
to.raie = from.get_value_or_null('-graie')
func _print_gutconfigs(values):
@ -217,7 +232,10 @@ func _run_tests(opt_resolver):
runner.set_gut_config(_gut_config)
get_tree().root.add_child(runner)
runner.run_tests()
if(opt_resolver.cmd_opts.raie):
runner.run_from_editor()
else:
runner.run_tests()
# parse options and run Gut

View file

@ -1 +1 @@
uid://ddx1kavbbraby
uid://bhuudqinp4bth

View file

@ -42,7 +42,8 @@
## created Option instance. See that class above for more info. You can use
## the returned instance to get values, or use get_value/get_value_or_null.
## add("--name", "default", "Description goes here")
## add_required("--name", "default", "Description goes here")
## add(["--name", "--aliases"], "default", "Description goes here")
## add_required(["--name", "--aliases"], "default", "Description goes here")
## add_positional("--name", "default", "Description goes here")
## add_positional_required("--name", "default", "Description goes here")
##
@ -153,6 +154,8 @@ class Option:
var default = null
var description = ''
var required = false
var aliases: Array[String] = []
var show_in_help = true
func _init(name,default_value,desc=''):
@ -162,12 +165,51 @@ class Option:
_value = default
func to_s(min_space=0):
func wrap_text(text, left_indent, max_length, wiggle_room=15):
var line_indent = str("\n", " ".repeat(left_indent + 1))
var wrapped = ''
var position = 0
var split_length = max_length
while(position < text.length()):
if(position > 0):
wrapped += line_indent
var split_by = split_length
if(position + split_by + wiggle_room >= text.length()):
split_by = text.length() - position
else:
var min_space = text.rfind(' ', position + split_length)
var max_space = text.find(' ', position + split_length)
if(max_space <= position + split_length + wiggle_room):
split_by = max_space - position
else:
split_by = min_space - position
wrapped += text.substr(position, split_by).lstrip(' ')
if(position == 0):
split_length = max_length - left_indent
position += split_by
return wrapped
func to_s(min_space=0, wrap_length=100):
var line_indent = str("\n", " ".repeat(min_space + 1))
var subbed_desc = description
if not aliases.is_empty():
subbed_desc += "\naliases: " + ", ".join(aliases)
subbed_desc = subbed_desc.replace('[default]', str(default))
subbed_desc = subbed_desc.replace("\n", line_indent)
return str(option_name.rpad(min_space), ' ', subbed_desc)
var final = str(option_name.rpad(min_space), ' ', subbed_desc)
if(wrap_length != -1):
final = wrap_text(final, min_space, wrap_length)
return final
func has_been_set():
@ -196,7 +238,7 @@ class Options:
var default_heading = OptionHeading.new()
var script_option = Option.new('-s', '?', 'script option provided by Godot')
var _options_by_name = {}
var _options_by_name = {"--script": script_option, "-s": script_option}
var _options_by_heading = [default_heading]
var _cur_heading = default_heading
@ -208,11 +250,16 @@ class Options:
_options_by_heading.append(heading)
func add(option):
func add(option, aliases=null):
options.append(option)
_options_by_name[option.option_name] = option
_cur_heading.options.append(option)
if aliases != null:
for a in aliases:
_options_by_name[a] = option
option.aliases.assign(aliases)
func add_positional(option):
positional.append(option)
@ -221,9 +268,7 @@ class Options:
func get_by_name(option_name):
var found_param = null
if(option_name == script_option.option_name):
found_param = script_option
elif(_options_by_name.has(option_name)):
if(_options_by_name.has(option_name)):
found_param = _options_by_name[option_name]
return found_param
@ -240,8 +285,8 @@ class Options:
if(heading != default_heading):
text += str("\n", heading.display, "\n")
for option in heading.options:
text += str(' ', option.to_s(longest + 2).replace("\n", "\n "), "\n")
if(option.show_in_help):
text += str(' ', option.to_s(longest + 2).replace("\n", "\n "), "\n")
return text
@ -296,19 +341,28 @@ class Options:
#-------------------------------------------------------------------------------
#
# optarse
#
#-------------------------------------------------------------------------------
var options = Options.new()
var banner = ''
var option_name_prefix = '-'
## @ignore
var options := Options.new()
## Set the banner property to any text you want to appear before the usage and
## options sections when printing the options help.
var banner := ''
## optparse uses option_name_prefix to differentiate between option names and
## values. Any argument that starts with this value will be treated as an
## argument name. The default is "-". Set this before calling parse if you want
## to change it.
var option_name_prefix := '-'
## @ignore
var unused = []
## @ignore
var parsed_args = []
var values = {}
## @ignore
var values: Dictionary = {}
func _populate_values_dictionary():
for entry in options.options:
@ -320,7 +374,6 @@ func _populate_values_dictionary():
values[value_key] = entry.value
func _convert_value_to_array(raw_value):
var split = raw_value.split(',')
# This is what an empty set looks like from the command line. If we do
@ -330,7 +383,6 @@ func _convert_value_to_array(raw_value):
split = []
return split
# REMEMBER raw_value not used for bools.
func _set_option_value(option, raw_value):
var t = typeof(option.default)
@ -403,30 +455,87 @@ func _parse_command_line_arguments(args):
return parsed_opts
func is_option(arg):
## Test if something is an existing argument. If [code]str(arg)[/code] begins
## with the [member option_name_prefix], it will considered true,
## otherwise it will be considered false.
func is_option(arg) -> bool:
return str(arg).begins_with(option_name_prefix)
func add(op_name, default, desc):
var new_op = null
## Adds a command line option.
## If [param op_names] is a String, this is set as the argument's name.
## If [param op_names] is an Array of Strings, all elements of the array
## will be aliases for the same argument and will be treated as such during
## parsing.
## [param default] is the default value the option will be set to if it is not
## explicitly set during parsing.
## [param desc] is a human readable text description of the option.
## If the option is successfully added, the Option object will be returned.
## If the option is not successfully added (e.g. a name collision with another
## option occurs), an error message will be printed and [code]null[/code]
## will be returned.
func add(op_names, default, desc: String) -> Option:
var op_name: String
var aliases: Array[String] = []
var new_op: Option = null
if(typeof(op_names) == TYPE_STRING):
op_name = op_names
else:
op_name = op_names[0]
aliases.assign(op_names.slice(1))
var bad_alias: int = aliases.map(
func (a: String) -> bool: return options.get_by_name(a) != null
).find(true)
if(options.get_by_name(op_name) != null):
push_error(str('Option [', op_name, '] already exists.'))
elif bad_alias != -1:
push_error(str('Option [', aliases[bad_alias], '] already exists.'))
else:
new_op = Option.new(op_name, default, desc)
options.add(new_op)
options.add(new_op, aliases)
return new_op
func add_required(op_name, default, desc):
var op = add(op_name, default, desc)
## Adds a required command line option.
## Required options that have not been set may be collected after parsing
## by calling [method get_missing_required_options].
## If [param op_names] is a String, this is set as the argument's name.
## If [param op_names] is an Array of Strings, all elements of the array
## will be aliases for the same argument and will be treated as such during
## parsing.
## [param default] is the default value the option will be set to if it is not
## explicitly set during parsing.
## [param desc] is a human readable text description of the option.
## If the option is successfully added, the Option object will be returned.
## If the option is not successfully added (e.g. a name collision with another
## option occurs), an error message will be printed and [code]null[/code]
## will be returned.
func add_required(op_names, default, desc: String) -> Option:
var op := add(op_names, default, desc)
if(op != null):
op.required = true
return op
func add_positional(op_name, default, desc):
## Adds a positional command line option.
## Positional options are parsed by their position in the list of arguments
## are are not assigned by name by the user.
## If [param op_name] is a String, this is set as the argument's name.
## If [param op_name] is an Array of Strings, all elements of the array
## will be aliases for the same argument and will be treated as such during
## parsing.
## [param default] is the default value the option will be set to if it is not
## explicitly set during parsing.
## [param desc] is a human readable text description of the option.
## If the option is successfully added, the Option object will be returned.
## If the option is not successfully added (e.g. a name collision with another
## option occurs), an error message will be printed and [code]null[/code]
## will be returned.
func add_positional(op_name, default, desc: String) -> Option:
var new_op = null
if(options.get_by_name(op_name) != null):
push_error(str('Positional option [', op_name, '] already exists.'))
@ -436,35 +545,64 @@ func add_positional(op_name, default, desc):
return new_op
func add_positional_required(op_name, default, desc):
## Adds a required positional command line option.
## If [param op_name] is a String, this is set as the argument's name.
## Required options that have not been set may be collected after parsing
## by calling [method get_missing_required_options].
## Positional options are parsed by their position in the list of arguments
## are are not assigned by name by the user.
## If [param op_name] is an Array of Strings, all elements of the array
## will be aliases for the same argument and will be treated as such during
## parsing.
## [param default] is the default value the option will be set to if it is not
## explicitly set during parsing.
## [param desc] is a human readable text description of the option.
## If the option is successfully added, the Option object will be returned.
## If the option is not successfully added (e.g. a name collision with another
## option occurs), an error message will be printed and [code]null[/code]
## will be returned.
func add_positional_required(op_name, default, desc: String) -> Option:
var op = add_positional(op_name, default, desc)
if(op != null):
op.required = true
return op
func add_heading(display_text):
## Headings are used to separate logical groups of command line options
## when printing out options from the help menu.
## Headings are printed out between option descriptions in the order
## that [method add_heading] was called.
func add_heading(display_text: String) -> void:
options.add_heading(display_text)
func get_value(name):
var found_param = options.get_by_name(name)
## Gets the value assigned to an option after parsing.
## [param name] can be the name of the option or an alias of it.
## [param name] specifies the option whose value you wish to query.
## If the option exists, the value assigned to it during parsing is returned.
## Otherwise, an error message is printed and [code]null[/code] is returned.
func get_value(name: String):
var found_param: Option = options.get_by_name(name)
if(found_param != null):
return found_param.value
else:
print("COULD NOT FIND OPTION " + name)
push_error("COULD NOT FIND OPTION " + name)
return null
# This will return null instead of the default value if an option has not been
# specified. This can be useful when providing an order of precedence to your
# values. For example if
# default value < config file < command line
# then you do not want to get the default value for a command line option or it
# will overwrite the value in a config file.
func get_value_or_null(name):
var found_param = options.get_by_name(name)
## Gets the value assigned to an option after parsing,
## returning null if the option was not assigned instead of its default value.
## [param name] specifies the option whose value you wish to query.
## This can be useful when providing an order of precedence to your values.
## For example if
## [codeblock]
## default value < config file < command line
## [/codeblock]
## then you do not want to get the default value for a command line option or
## it will overwrite the value in a config file.
func get_value_or_null(name: String):
var found_param: Option = options.get_by_name(name)
if(found_param != null and found_param.has_been_set()):
return found_param.value
@ -472,10 +610,11 @@ func get_value_or_null(name):
return null
func get_help():
var sep = '---------------------------------------------------------'
## Returns the help text for all defined options.
func get_help() -> String:
var sep := '---------------------------------------------------------'
var text = str(sep, "\n", banner, "\n\n")
var text := str(sep, "\n", banner, "\n\n")
text += "Usage\n-----------\n"
text += " " + options.get_usage_text() + "\n\n"
text += "\nOptions\n-----------\n"
@ -484,11 +623,18 @@ func get_help():
return text
func print_help():
## Prints out the help text for all defined options.
func print_help() -> void:
print(get_help())
func parse(cli_args=null):
## Parses a string for all options that have been set in this optparse.
## if [param cli_args] is passed as a String, then it is parsed.
## Otherwise if [param cli_args] is null,
## aruments passed to the Godot engine at startup are parsed.
## See the explanation at the top of addons/gut/cli/optparse.gd to understand
## which arguments this will have access to.
func parse(cli_args=null) -> void:
parsed_args = cli_args
if(parsed_args == null):
@ -499,7 +645,9 @@ func parse(cli_args=null):
_populate_values_dictionary()
func get_missing_required_options():
## Get all options that were required and were not set during parsing.
## The return value is an Array of Options.
func get_missing_required_options() -> Array:
return options.get_missing_required_options()

View file

@ -1 +1 @@
uid://bt37la38fetnn
uid://c8m4fojwln6bq

View file

@ -42,7 +42,9 @@ func _init(logger=null):
func get_new():
return load_script().new()
var inst = load_script().new()
inst.collected_script = self
return inst
func load_script():

View file

@ -1 +1 @@
uid://ckts04qa5iehe
uid://bjjcnr1oqvag6

View file

@ -40,6 +40,8 @@ var orphans = 0
var was_run = false
var collected_script : WeakRef = null
func did_pass():
return is_passing()

View file

@ -1 +1 @@
uid://cdsm0ov7xq73d
uid://cl854f1m26a2a

View file

@ -1 +1 @@
uid://73ft8c27puxf
uid://bohry7fhscy7y

View file

@ -1 +1 @@
uid://d81wg1ecbtxu
uid://cow1xqmqqvn4e

View file

@ -1 +1 @@
uid://0m6qaxiiiwwq
uid://ch2km05phxacd

View file

@ -7,7 +7,6 @@ enum {
var _strutils = GutUtils.Strutils.new()
var _compare = GutUtils.Comparator.new()
var DiffTool = load('res://addons/gut/diff_tool.gd')
var _value_1 = null
var _value_2 = null

View file

@ -1 +1 @@
uid://epp6hsk6gg3t
uid://beoxokvl1hjs8

View file

@ -0,0 +1,22 @@
var __gutdbl_values = {
thepath = '{path}',
subpath = '{subpath}',
stubber = {stubber_id},
spy = {spy_id},
gut = {gut_id},
singleton_name = '{singleton_name}',
singleton = {singleton_id},
is_partial = {is_partial},
doubled_methods = {doubled_methods},
}
var __gutdbl = load('res://addons/gut/double_tools.gd').new(self)
# Here so other things can check for a method to know if this is a double.
func __gutdbl_check_method__():
pass
# Cleanup called by GUT after tests have finished. Important for RefCounted
# objects. Nodes are freed, and won't have this method called on them.
func __gutdbl_done():
__gutdbl = null
__gutdbl_values.clear()

View file

@ -1,6 +1,9 @@
{func_decleration}
{vararg_warning}__gutdbl.spy_on('{method_name}', {param_array})
if(__gutdbl == null):
return
__gutdbl.spy_on('{method_name}', {param_array})
if(__gutdbl.is_stubbed_to_call_super('{method_name}', {param_array})):
return {super_call}
{super_call}
else:
return await __gutdbl.handle_other_stubs('{method_name}', {param_array})

View file

@ -3,29 +3,10 @@
# ##############################################################################
{extends}
{constants}
{properties}
# ------------------------------------------------------------------------------
# GUT stuff
# ------------------------------------------------------------------------------
var __gutdbl_values = {
double = self,
thepath = '{path}',
subpath = '{subpath}',
stubber = {stubber_id},
spy = {spy_id},
gut = {gut_id},
from_singleton = '{singleton_name}',
is_partial = {is_partial},
doubled_methods = {doubled_methods},
}
var __gutdbl = load('res://addons/gut/double_tools.gd').new(__gutdbl_values)
# Here so other things can check for a method to know if this is a double.
func __gutdbl_check_method__():
pass
{double_data}
# ------------------------------------------------------------------------------
# Doubled Methods

View file

@ -0,0 +1,16 @@
{extends}
{constants}
{signals}
{properties}
# ------------------------------------------------------------------------------
# GUT stuff
# ------------------------------------------------------------------------------
{double_data}
# ------------------------------------------------------------------------------
# Doubled Methods
# ------------------------------------------------------------------------------

View file

@ -1,77 +1,83 @@
var thepath = ''
var subpath = ''
var stubber = null
var spy = null
var gut = null
var from_singleton = null
var singleton_name = null
var is_partial = null
var double = null
var double_ref : WeakRef = null
var stubber_ref : WeakRef = null
var spy_ref : WeakRef = null
var gut_ref : WeakRef = null
var singleton_ref : WeakRef = null
var __gutdbl_values = {}
const NO_DEFAULT_VALUE = '!__gut__no__default__value__!'
func _init(values=null):
if(values != null):
double = values.double
func _init(double = null):
if(double != null):
var values = double.__gutdbl_values
__gutdbl_values = double.__gutdbl_values
double_ref = weakref(double)
thepath = values.thepath
subpath = values.subpath
stubber = from_id(values.stubber)
spy = from_id(values.spy)
gut = from_id(values.gut)
from_singleton = values.from_singleton
stubber_ref = weakref_from_id(values.stubber)
spy_ref = weakref_from_id(values.spy)
gut_ref = weakref_from_id(values.gut)
singleton_ref = weakref_from_id(values.singleton)
singleton_name = values.singleton_name
is_partial = values.is_partial
if(gut != null):
gut.get_autofree().add_free(double)
if(gut_ref.get_ref() != null):
gut_ref.get_ref().get_autofree().add_free(double_ref.get_ref())
func _get_stubbed_method_to_call(method_name, called_with):
var method = stubber.get_call_this(double, method_name, called_with)
var method = stubber_ref.get_ref().get_call_this(double_ref.get_ref(), method_name, called_with)
if(method != null):
method = method.bindv(called_with)
return method
return method
func from_id(inst_id):
func weakref_from_id(inst_id):
if(inst_id == -1):
return null
return weakref(null)
else:
return instance_from_id(inst_id)
return weakref(instance_from_id(inst_id))
func is_stubbed_to_call_super(method_name, called_with):
if(stubber != null):
return stubber.should_call_super(double, method_name, called_with)
if(stubber_ref.get_ref() != null):
return stubber_ref.get_ref().should_call_super(double_ref.get_ref(), method_name, called_with)
else:
return false
func handle_other_stubs(method_name, called_with):
if(stubber == null):
if(stubber_ref.get_ref() == null):
return
var method = _get_stubbed_method_to_call(method_name, called_with)
if(method != null):
return await method.call()
else:
return stubber.get_return(double, method_name, called_with)
return stubber_ref.get_ref().get_return(double_ref.get_ref(), method_name, called_with)
func spy_on(method_name, called_with):
if(spy != null):
spy.add_call(double, method_name, called_with)
if(spy_ref.get_ref() != null):
spy_ref.get_ref().add_call(double_ref.get_ref(), method_name, called_with)
func default_val(method_name, p_index, default_val=NO_DEFAULT_VALUE):
if(stubber != null):
return stubber.get_default_value(double, method_name, p_index)
else:
func default_val(method_name, p_index):
if(stubber_ref.get_ref() == null):
return null
else:
var result = stubber_ref.get_ref().get_default_value(double_ref.get_ref(), method_name, p_index)
return result
func vararg_warning():
if(gut != null):
gut.get_logger().warn(
"This method contains a vararg argument and the paramter count was not stubbed. " + \
"GUT adds extra parameters to this method which should fill most needs. " + \
"It is recommended that you stub param_count for this object's class to ensure " + \
"that there are not any parameter count mismatch errors.")
func get_singleton():
var to_return = singleton_ref.get_ref()
if(to_return == null):
push_error("Trying to get a singleton reference on a non-singleton double: ",
__gutdbl_values.singleton_name, "/", __gutdbl_values.singleton)
return to_return

View file

@ -1 +1 @@
uid://bs61eaaw6d85b
uid://tr4khoco1hef

View file

@ -1,47 +1,13 @@
# ------------------------------------------------------------------------------
# A stroke of genius if I do say so. This allows for doubling a scene without
# having to write any files. By overloading the "instantiate" method we can
# make whatever we want.
# ------------------------------------------------------------------------------
class PackedSceneDouble:
extends PackedScene
var _script = null
var _scene = null
func set_script_obj(obj):
_script = obj
@warning_ignore("native_method_override")
func instantiate(edit_state=0):
var inst = _scene.instantiate(edit_state)
var export_props = []
var script_export_flag = (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_SCRIPT_VARIABLE)
if(_script != null):
if(inst.get_script() != null):
# Get all the exported props and values so we can set them again
for prop in inst.get_property_list():
var is_export = prop.usage & (script_export_flag) == script_export_flag
if(is_export):
export_props.append([prop.name, inst.get(prop.name)])
inst.set_script(_script)
for exported_value in export_props:
inst.set(exported_value[0], exported_value[1])
return inst
func load_scene(path):
_scene = load(path)
extends RefCounted
static var _base_script_text = GutUtils.get_file_as_text('res://addons/gut/double_templates/script_template.txt')
static var _singleton_script_text = GutUtils.get_file_as_text('res://addons/gut/double_templates/singleton_template.txt')
static var _double_data_text = GutUtils.get_file_as_text('res://addons/gut/double_templates/double_data_template.txt')
# ------------------------------------------------------------------------------
# START Doubler
# ------------------------------------------------------------------------------
var _base_script_text = GutUtils.get_file_as_text('res://addons/gut/double_templates/script_template.txt')
var _script_collector = GutUtils.ScriptCollector.new()
var _singleton_parser = GutUtils.SingletonParser.new()
# used by tests for debugging purposes.
var print_source = false
var inner_class_registry = GutUtils.InnerClassRegistry.new()
@ -83,7 +49,6 @@ func set_strategy(strategy):
else:
_lgr.error(str('doubler.gd: invalid double strategy ', strategy))
var _method_maker = GutUtils.MethodMaker.new()
func get_method_maker():
return _method_maker
@ -92,6 +57,7 @@ var _ignored_methods = GutUtils.OneToMany.new()
func get_ignored_methods():
return _ignored_methods
# ###############
# Private
# ###############
@ -100,6 +66,11 @@ func _init(strategy=GutUtils.DOUBLE_STRATEGY.SCRIPT_ONLY):
_strategy = strategy
func _notification(what: int) -> void:
if(what == NOTIFICATION_PREDELETE):
if(_stubber != null):
_stubber.clear()
func _get_indented_line(indents, text):
var to_return = ''
for _i in range(indents):
@ -111,8 +82,13 @@ func _stub_to_call_super(parsed, method_name):
if(!parsed.get_method(method_name).is_eligible_for_doubling()):
return
var params = GutUtils.StubParams.new(parsed.script_path, method_name, parsed.subpath)
var params = null
if(parsed.is_native):
params = GutUtils.StubParams.new(parsed._native_class, method_name, parsed.subpath)
else:
params = GutUtils.StubParams.new(parsed.script_path, method_name, parsed.subpath)
params.to_call_super()
params.is_script_default = true
_stubber.add_stub(params)
@ -134,27 +110,63 @@ func _get_base_script_text(parsed, override_path, partial, included_methods):
gut_id = _gut.get_instance_id()
var extends_text = parsed.get_extends_text()
var values = {
# Top sections
"extends":extends_text,
"constants":'',#obj_info.get_constants_text(),
"properties":'',#obj_info.get_properties_text(),
# metadata values
var double_data_values = {
"path":path,
"subpath":GutUtils.nvl(parsed.subpath, ''),
"stubber_id":stubber_id,
"spy_id":spy_id,
"gut_id":gut_id,
"singleton_name":'',#GutUtils.nvl(obj_info.get_singleton_name(), ''),
"singleton_name":'',
"singleton_id":-1,
"is_partial":partial,
"doubled_methods":included_methods,
}
var values = {
"extends":extends_text,
"double_data":_double_data_text.format(double_data_values),
}
return _base_script_text.format(values)
func _get_singleton_text(parsed, included_methods, is_partial):
var stubber_id = -1
if(_stubber != null):
stubber_id = _stubber.get_instance_id()
var spy_id = -1
if(_spy != null):
spy_id = _spy.get_instance_id()
var gut_id = -1
if(_gut != null):
gut_id = _gut.get_instance_id()
var double_data_values = {
"path":'',
"subpath":'',
"stubber_id":stubber_id,
"spy_id":spy_id,
"gut_id":gut_id,
"singleton_name":parsed.singleton_name,
"singleton_id":parsed.singleton_id,
"is_partial":is_partial,
"doubled_methods":included_methods,
}
var values = {
"extends":"extends RefCounted",
"double_data":_double_data_text.format(double_data_values),
"signals":parsed.get_all_signal_text(),
"constants":parsed.get_all_constants_text(),
"properties":parsed.get_all_properties_text()
}
var src = _singleton_script_text.format(values)
return src
func _is_method_eligible_for_doubling(parsed_script, parsed_method):
return !parsed_method.is_accessor() and \
parsed_method.is_eligible_for_doubling() and \
@ -177,66 +189,89 @@ func _create_script_no_warnings(src):
func _create_double(parsed, strategy, override_path, partial):
var path = ""
path = parsed.script_path
var dbl_src = ""
var included_methods = []
for method in parsed.get_local_methods():
if(_is_method_eligible_for_doubling(parsed, method)):
included_methods.append(method.meta.name)
var mthd = parsed.get_local_method(method.meta.name)
if(parsed.is_native):
dbl_src += _get_func_text(method.meta, parsed.resource)
else:
dbl_src += _get_func_text(method.meta, path)
dbl_src += _get_func_text(method.meta)
if(strategy == GutUtils.DOUBLE_STRATEGY.INCLUDE_NATIVE):
for method in parsed.get_super_methods():
if(_is_method_eligible_for_doubling(parsed, method)):
included_methods.append(method.meta.name)
_stub_to_call_super(parsed, method.meta.name)
if(parsed.is_native):
dbl_src += _get_func_text(method.meta, parsed.resource)
else:
dbl_src += _get_func_text(method.meta, path)
dbl_src += _get_func_text(method.meta)
var base_script = _get_base_script_text(parsed, override_path, partial, included_methods)
dbl_src = base_script + "\n\n" + dbl_src
if(print_source):
print(GutUtils.add_line_numbers(dbl_src))
var to_print :String = GutUtils.add_line_numbers(dbl_src)
to_print = to_print.rstrip("\n")
_lgr.log(str(to_print))
var DblClass = _create_script_no_warnings(dbl_src)
if(_stubber != null):
_stub_method_default_values(DblClass, parsed, strategy)
_stub_method_default_values(parsed)
if(print_source):
_lgr.log(str(" path | ", DblClass.resource_path, "\n"))
return DblClass
func _stub_method_default_values(which, parsed, strategy):
func _create_singleton_double(singleton, is_partial):
var parsed = _singleton_parser.parse(singleton)
var dbl_src = _get_singleton_text(parsed, parsed.methods_by_name.keys(), is_partial)
for key in parsed.methods_by_name:
if(!_ignored_methods.has(singleton, key)):
dbl_src += _method_maker.get_function_text(parsed.methods_by_name[key], singleton) + "\n"
if(print_source):
var to_print :String = GutUtils.add_line_numbers(dbl_src)
to_print = to_print.rstrip("\n")
_lgr.log(str(to_print))
var DblClass = GutUtils.create_script_from_source(dbl_src)
if(_stubber != null):
for key in parsed.methods_by_name:
var meta = parsed.methods_by_name[key]
if(meta != {} and !meta.flags & METHOD_FLAG_VARARG):
_stubber.stub_defaults_from_meta(singleton, meta)
return DblClass
func _stub_method_default_values(parsed):
for method in parsed.get_local_methods():
if(method.is_eligible_for_doubling() and !_ignored_methods.has(parsed.resource, method.meta.name)):
_stubber.stub_defaults_from_meta(parsed.script_path, method.meta)
func _double_scene_and_script(scene, strategy, partial):
var to_return = PackedSceneDouble.new()
to_return.load_scene(scene.get_path())
var dbl_bundle = scene._bundled.duplicate(true)
var script_obj = GutUtils.get_scene_script_object(scene)
# I'm not sure if the script object for the root node of a packed scene is
# always the first entry in "variants" so this tries to find it.
var script_index = dbl_bundle["variants"].find(script_obj)
var script_dbl = null
if(script_obj != null):
var script_dbl = null
if(partial):
script_dbl = _partial_double(script_obj, strategy, scene.get_path())
else:
script_dbl = _double(script_obj, strategy, scene.get_path())
to_return.set_script_obj(script_dbl)
return to_return
if(script_index != -1):
dbl_bundle["variants"][script_index] = script_dbl
var doubled_scene = PackedScene.new()
doubled_scene._set_bundled_scene(dbl_bundle)
return doubled_scene
func _get_inst_id_ref_str(inst):
@ -246,14 +281,8 @@ func _get_inst_id_ref_str(inst):
return ref_str
func _get_func_text(method_hash, path):
var override_count = null;
if(_stubber != null):
override_count = _stubber.get_parameter_count(path, method_hash.name)
var text = _method_maker.get_function_text(method_hash, override_count) + "\n"
return text
func _get_func_text(method_hash):
return _method_maker.get_function_text(method_hash) + "\n"
func _parse_script(obj):
@ -291,6 +320,7 @@ func _partial_double(obj, strategy, override_path=null):
func double(obj, strategy=_strategy):
return _double(obj, strategy)
func partial_double(obj, strategy=_strategy):
return _partial_double(obj, strategy)
@ -299,6 +329,7 @@ func partial_double(obj, strategy=_strategy):
func double_scene(scene, strategy=_strategy):
return _double_scene_and_script(scene, strategy, false)
func partial_double_scene(scene, strategy=_strategy):
return _double_scene_and_script(scene, strategy, true)
@ -306,6 +337,7 @@ func partial_double_scene(scene, strategy=_strategy):
func double_gdnative(which):
return _double(which, GutUtils.DOUBLE_STRATEGY.INCLUDE_NATIVE)
func partial_double_gdnative(which):
return _partial_double(which, GutUtils.DOUBLE_STRATEGY.INCLUDE_NATIVE)
@ -320,11 +352,20 @@ func partial_double_inner(parent, inner, strategy=_strategy):
return _create_double(parsed, strategy, null, true)
func double_singleton(obj):
return _create_singleton_double(obj, false)
func partial_double_singleton(obj):
return _create_singleton_double(obj, true)
func add_ignored_method(obj, method_name):
_ignored_methods.add(obj, method_name)
# ##############################################################################
#(G)odot (U)nit (T)est class
#
@ -352,4 +393,4 @@ func add_ignored_method(obj, method_name):
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ##############################################################################
# ##############################################################################

View file

@ -1 +1 @@
uid://dw6wqc1uj6j0r
uid://cpy013l0wqwmg

View file

@ -1 +1 @@
uid://dc5bkpm84twcu
uid://cnbjsrik0p5uf

View file

@ -0,0 +1,212 @@
@tool
extends Node
# ##############################################################################
#
# Watches script editors and emits a signal whenever the method, inner class,
# or script changes based on cursor position and other stuff.
#
# Basically, whenever this thing's signal is emitted, then the RunAtCursor
# buttons should be updated to match the data passed to the signal.
# ##############################################################################
# In the editor, whenever a script is opened you get these new things that
# hang off of EditorInterface.get_script_editor()
# * ScriptEditorBase
# * CodeEdit
# ##############################################################################
var _last_info : Dictionary = {}
var _last_line = -1
# This is the control that holds all the individual editors.
var _current_script_editor : ScriptEditor = null
# Reference to the GDScript for the last script we were notified about.
var _current_script = null
var _current_script_is_test_script = false
var _current_editor_base : ScriptEditorBase = null
var _current_editor : CodeEdit = null
# Quick lookup of editors based on the current script.
var _editors_for_scripts : Dictionary= {}
# In order to keep the data that comes back from the emitted signal way more
# usable, we have to know what GUT looks for for an inner-test-class prefix.
# If we didn't do this, then this thing would have to return all the inner
# classes and then we'd have to determine if we were in an inner-test-class
# outside of here by traversing all the classes returned. It makes this thing
# less generic and know too much, but this is probably already too generic as
# it is.
var inner_class_prefix = "Test"
var method_prefix = "test_"
var script_prefix = "test_"
var script_suffix = ".gd"
# Based on cursor and open editors, this will be emitted. You do what you
# want with it.
signal it_changed(change_data)
func _ready():
# This will not change, and should not change, over the course of a session.
_current_script_editor = EditorInterface.get_script_editor()
_current_script_editor.editor_script_changed.connect(_on_editor_script_changed)
_current_script_editor.script_close.connect(_on_editor_script_close)
func _handle_caret_location(which):
var current_line = which.get_caret_line(0) + 1
if(_last_line != current_line):
_last_line = current_line
if(_current_script_is_test_script):
var new_info = _make_info(which, _current_script, _current_script_is_test_script)
if(_last_info != new_info):
_last_info = new_info
it_changed.emit(_last_info.duplicate())
func _get_func_name_from_line(text):
text = text.strip_edges()
var left = text.split("(")[0]
var func_name = left.split(" ")[1]
return func_name
func _get_class_name_from_line(text):
text = text.strip_edges()
var right = text.split(" ")[1]
var the_name = right.rstrip(":")
return the_name
func _make_info(editor, script, test_script_flag):
if(editor == null):
return
var info = {
script = script,
inner_class = null,
method = null,
is_test_script = test_script_flag
}
var start_line = editor.get_caret_line()
var line = start_line
var done_func = false
var done_inner = false
while(line > 0 and (!done_func or !done_inner)):
if(editor.can_fold_line(line)):
var text = editor.get_line(line)
var strip_text = text.strip_edges(true, false) # only left
if(!done_func and strip_text.begins_with("func ")):
info.method = _get_func_name_from_line(text)
done_func = true
# If the func line is left justified then there won't be any
# inner classes above it.
if(editor.get_indent_level(line) == 0):
done_inner = true
if(!done_inner and strip_text.begins_with("class")):
var inner_name = _get_class_name_from_line(text)
# See note about inner_class_prefix, this knows too much, but
# if it was to know less it would insanely more difficult
# everywhere.
if(inner_name.begins_with(inner_class_prefix)):
info.inner_class = inner_name
done_inner = true
done_func = true
line -= 1
# print('parsed lines: ', start_line - line, '(', info.inner_class, ':', info.method, ')')
return info
# -------------
# Events
# -------------
# Fired whenever the script changes. This does not fire for help files. If
# you click a help file and then back to the same file, then this will fire
# for the same script
#
# This does fire for some non-script files such as .cfg, .json and .md files,
# but the passed in value will be null.
#
# This can fire multiple times for the same script when a script is opened.
func _on_editor_script_changed(script):
if(script == null):
return
_last_line = -1
_current_script = script
_current_editor_base = _current_script_editor.get_current_editor()
if(_current_editor_base.get_base_editor() is CodeEdit):
_current_editor = _current_editor_base.get_base_editor()
if(!_current_editor.caret_changed.is_connected(_on_caret_changed)):
_current_editor.caret_changed.connect(_on_caret_changed.bind(_current_editor))
else:
_current_editor = null
_editors_for_scripts[script] = _current_editor
_current_script_is_test_script = is_test_script(_current_script)
_handle_caret_location(_current_editor)
func _on_editor_script_close(script):
var script_editor = _editors_for_scripts.get(script, null)
if(script_editor != null):
if(script_editor.caret_changed.is_connected(_on_caret_changed)):
script_editor.caret_changed.disconnect(_on_caret_changed)
_editors_for_scripts.erase(script)
func _on_caret_changed(which):
# Sometimes this is fired for editors that are not the current. I could
# make this fire by saving a file in an external editor. I was unable to
# get useful data out when it wasn't the current editor so I'm only doing
# anything when it is the current editor.
if(which == _current_editor):
_handle_caret_location(which)
func _could_be_test_script(script):
return script.resource_path.get_file().begins_with(script_prefix) and \
script.resource_path.get_file().ends_with(script_suffix)
# -------------
# Public
# -------------
var _scripts_that_have_been_warned_about = []
var _we_have_warned_enough = false
var _max_warnings = 5
func is_test_script(script):
var base = script.get_base_script()
if(base == null and script.get_script_method_list().size() == 0 and _could_be_test_script(script)):
if(OS.is_stdout_verbose() or (!_scripts_that_have_been_warned_about.has(script.resource_path) and !_we_have_warned_enough)):
_scripts_that_have_been_warned_about.append(script.resource_path)
push_warning(str('[GUT] Treating ', script.resource_path, " as test script: ",
"GUT was not able to retrieve information about this script. If this is ",
"a new script you can ignore this warning. Otherwise, this may ",
"have to do with having VSCode open. Restarting Godot sometimes helps. See ",
"https://github.com/bitwes/Gut/issues/754"))
if(!OS.is_stdout_verbose() and _scripts_that_have_been_warned_about.size() >= _max_warnings):
print("[GUT] Disabling warning.")
_we_have_warned_enough = true
# We can't know if this is a test script. It's more usable if we
# assume this is a test script.
return true
else:
while(base and base.resource_path != 'res://addons/gut/test.gd'):
base = base.get_base_script()
return base != null
func get_info():
return _last_info.duplicate()
func log_values():
print("---------------------------------------------------------------")
print("script ", _current_script)
print("script_editor ", _current_script_editor)
print("editor_base ", _current_editor_base)
print("editor ", _current_editor)

View file

@ -0,0 +1 @@
uid://c110s7a32x4su

192
addons/gut/error_tracker.gd Normal file
View file

@ -0,0 +1,192 @@
extends Logger
class_name GutErrorTracker
# ------------------------------------------------------------------------------
# Static methods wrap around add/remove logger to make disabling the logger
# easier and to help avoid misusing add/remove in tests. If GUT needs to
# add/remove a logger then this is how it should do it.
# ------------------------------------------------------------------------------
static var registered_loggers := {}
static var register_loggers = true
static func register_logger(which):
if(register_loggers and !registered_loggers.has(which)):
OS.add_logger(which)
registered_loggers[which] = get_stack()
static func deregister_logger(which):
if(registered_loggers.has(which)):
OS.remove_logger(which)
registered_loggers.erase(which)
# ------------------------------------------------------------------------------
# GutErrorTracker
# ------------------------------------------------------------------------------
var _current_test_id = GutUtils.NO_TEST
var _mutex = Mutex.new()
var errors = GutUtils.OneToMany.new()
var treat_gut_errors_as : GutUtils.TREAT_AS = GutUtils.TREAT_AS.FAILURE
var treat_engine_errors_as : GutUtils.TREAT_AS = GutUtils.TREAT_AS.FAILURE
var treat_push_error_as : GutUtils.TREAT_AS = GutUtils.TREAT_AS.FAILURE
var disabled = false
# ----------------
#region Private
# ----------------
func _get_stack_data(current_test_name):
var test_entry = {}
var stackTrace = get_stack()
if(stackTrace!=null):
var index = 0
while(index < stackTrace.size() and test_entry == {}):
var line = stackTrace[index]
var function = line.get("function")
if function == current_test_name:
test_entry = stackTrace[index]
else:
index += 1
for i in range(index):
stackTrace.remove_at(0)
return {
"test_entry" = test_entry,
"full_stack" = stackTrace
}
func _is_error_failable(error : GutTrackedError):
var is_it = false
if(error.handled == false):
if(error.is_gut_error()):
is_it = treat_gut_errors_as == GutUtils.TREAT_AS.FAILURE
elif(error.is_push_error()):
is_it = treat_push_error_as == GutUtils.TREAT_AS.FAILURE
elif(error.is_engine_error()):
is_it = treat_engine_errors_as == GutUtils.TREAT_AS.FAILURE
return is_it
# ----------------
#endregion
#region Godot's Logger Overrides
# ----------------
# Godot's Logger virtual method for errors
func _log_error(function: String, file: String, line: int,
code: String, rationale: String, editor_notify: bool,
error_type: int, script_backtraces: Array[ScriptBacktrace]) -> void:
add_error(function, file, line,
code, rationale, editor_notify,
error_type, script_backtraces)
# Godot's Logger virtual method for any output?
# func _log_message(message: String, error: bool) -> void:
# pass
# ----------------
#endregion
#region Public
# ----------------
func start_test(test_id):
_current_test_id = test_id
func end_test():
_current_test_id = GutUtils.NO_TEST
func did_test_error(test_id=_current_test_id):
return errors.size(test_id) > 0
func get_current_test_errors():
return errors.items.get(_current_test_id, [])
# This should look through all the errors for a test and see if a failure
# should happen based off of flags.
func should_test_fail_from_errors(test_id = _current_test_id):
var to_return = false
if(errors.items.has(test_id)):
var errs = errors.items[test_id]
var index = 0
while(index < errs.size() and !to_return):
var error = errs[index]
to_return = _is_error_failable(error)
index += 1
return to_return
func get_errors_for_test(test_id=_current_test_id):
var to_return = []
if(errors.items.has(test_id)):
to_return = errors.items[test_id].duplicate()
return to_return
# Returns emtpy string or text for errors that occurred during the test that
# should cause failure based on this class' flags.
func get_fail_text_for_errors(test_id=_current_test_id) -> String:
var error_texts = []
if(errors.items.has(test_id)):
for error in errors.items[test_id]:
if(_is_error_failable(error)):
error_texts.append(str('<', error.get_error_type_name(), '>', error.code))
var to_return = ""
for i in error_texts.size():
if(to_return != ""):
to_return += "\n"
to_return += str("[", i + 1, "] ", error_texts[i])
return to_return
func add_gut_error(text) -> GutTrackedError:
if(_current_test_id != GutUtils.NO_TEST):
var data = _get_stack_data(_current_test_id)
if(data.test_entry != {}):
return add_error(_current_test_id, data.test_entry.source, data.test_entry.line,
text, '', false,
GutUtils.GUT_ERROR_TYPE, data.full_stack)
return add_error(_current_test_id, "unknown", -1,
text, '', false,
GutUtils.GUT_ERROR_TYPE, get_stack())
func add_error(function: String, file: String, line: int,
code: String, rationale: String, editor_notify: bool,
error_type: int, script_backtraces: Array) -> GutTrackedError:
if(disabled):
return
_mutex.lock()
var err := GutTrackedError.new()
err.backtrace = script_backtraces
err.code = code
err.rationale = rationale
err.error_type = error_type
err.editor_notify = editor_notify
err.file = file
err.function = function
err.line = line
errors.add(_current_test_id, err)
_mutex.unlock()
return err

View file

@ -0,0 +1 @@
uid://35kxgqotjmlu

View file

@ -0,0 +1,6 @@
# This file is here so we can load it only when we are in the editor so that
# other places do not have to have "EditorInterface" in them, which causes a
# parser error when loaded outside of the editor. The things we have to do in
# order to test things is annoying.
func get_it():
return EditorInterface

View file

@ -0,0 +1 @@
uid://fgn2xo217kg1

View file

@ -0,0 +1,48 @@
# This file is auto-generated as part of the release process. GUT maintainers
# should not change this file manually.
static var class_ref = [
AudioServer,
CameraServer,
ClassDB,
DisplayServer,
# excluded: EditorInterface,
Engine,
EngineDebugger,
GDExtensionManager,
Geometry2D,
Geometry3D,
IP,
Input,
InputMap,
JavaClassWrapper,
JavaScriptBridge,
Marshalls,
NativeMenu,
NavigationMeshGenerator,
NavigationServer2D,
NavigationServer2DManager,
NavigationServer3D,
NavigationServer3DManager,
OS,
Performance,
PhysicsServer2D,
PhysicsServer2DManager,
PhysicsServer3D,
PhysicsServer3DManager,
ProjectSettings,
RenderingServer,
ResourceLoader,
ResourceSaver,
ResourceUID,
TextServerManager,
ThemeDB,
Time,
TranslationServer,
WorkerThreadPool,
XRServer
]
static var names = []
static func _static_init():
for entry in class_ref:
names.append(entry.get_class())

View file

@ -0,0 +1 @@
uid://cjkrqx36xfcbw

View file

@ -1,139 +0,0 @@
@tool
extends Window
var GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')
var default_path = GutEditorGlobals.editor_shortcuts_path
@onready var _ctrls = {
run_all = $Layout/CRunAll/ShortcutButton,
run_current_script = $Layout/CRunCurrentScript/ShortcutButton,
run_current_inner = $Layout/CRunCurrentInner/ShortcutButton,
run_current_test = $Layout/CRunCurrentTest/ShortcutButton,
panel_button = $Layout/CPanelButton/ShortcutButton,
}
var _user_prefs = GutEditorGlobals.user_prefs
func _ready():
for key in _ctrls:
var sc_button = _ctrls[key]
sc_button.connect('start_edit', _on_edit_start.bind(sc_button))
sc_button.connect('end_edit', _on_edit_end)
# show dialog when running scene from editor.
if(get_parent() == get_tree().root):
popup_centered()
func _cancel_all():
_ctrls.run_all.cancel()
_ctrls.run_current_script.cancel()
_ctrls.run_current_inner.cancel()
_ctrls.run_current_test.cancel()
_ctrls.panel_button.cancel()
# ------------
# Events
# ------------
func _on_Hide_pressed():
hide()
func _on_edit_start(which):
for key in _ctrls:
var sc_button = _ctrls[key]
if(sc_button != which):
sc_button.disable_set(true)
sc_button.disable_clear(true)
func _on_edit_end():
for key in _ctrls:
var sc_button = _ctrls[key]
sc_button.disable_set(false)
sc_button.disable_clear(false)
func _on_popup_hide():
_cancel_all()
# ------------
# Public
# ------------
func get_run_all():
return _ctrls.run_all.get_shortcut()
func get_run_current_script():
return _ctrls.run_current_script.get_shortcut()
func get_run_current_inner():
return _ctrls.run_current_inner.get_shortcut()
func get_run_current_test():
return _ctrls.run_current_test.get_shortcut()
func get_panel_button():
return _ctrls.panel_button.get_shortcut()
func _set_pref_value(pref, button):
pref.value = {shortcut = button.get_shortcut().events}
func save_shortcuts():
save_shortcuts_to_file(default_path)
func save_shortcuts_to_editor_settings():
_set_pref_value(_user_prefs.shortcut_run_all, _ctrls.run_all)
_set_pref_value(_user_prefs.shortcut_run_current_script, _ctrls.run_current_script)
_set_pref_value(_user_prefs.shortcut_run_current_inner, _ctrls.run_current_inner)
_set_pref_value(_user_prefs.shortcut_run_current_test, _ctrls.run_current_test)
_set_pref_value(_user_prefs.shortcut_panel_button, _ctrls.panel_button)
_user_prefs.save_it()
func save_shortcuts_to_file(path):
var f = ConfigFile.new()
f.set_value('main', 'run_all', _ctrls.run_all.get_shortcut())
f.set_value('main', 'run_current_script', _ctrls.run_current_script.get_shortcut())
f.set_value('main', 'run_current_inner', _ctrls.run_current_inner.get_shortcut())
f.set_value('main', 'run_current_test', _ctrls.run_current_test.get_shortcut())
f.set_value('main', 'panel_button', _ctrls.panel_button.get_shortcut())
f.save(path)
func _load_shortcut_from_pref(user_pref):
var to_return = Shortcut.new()
# value with be _user_prefs.EMPTY which is a string when the value
# has not been set.
if(typeof(user_pref.value) == TYPE_DICTIONARY):
to_return.events.append(user_pref.value.shortcut[0])
# to_return = user_pref.value
return to_return
func load_shortcuts():
load_shortcuts_from_file(default_path)
func load_shortcuts_from_editor_settings():
var empty = Shortcut.new()
_ctrls.run_all.set_shortcut(_load_shortcut_from_pref(_user_prefs.shortcut_run_all))
_ctrls.run_current_script.set_shortcut(_load_shortcut_from_pref(_user_prefs.shortcut_run_current_script))
_ctrls.run_current_inner.set_shortcut(_load_shortcut_from_pref(_user_prefs.shortcut_run_current_inner))
_ctrls.run_current_test.set_shortcut(_load_shortcut_from_pref(_user_prefs.shortcut_run_current_test))
_ctrls.panel_button.set_shortcut(_load_shortcut_from_pref(_user_prefs.shortcut_panel_button))
func load_shortcuts_from_file(path):
var f = ConfigFile.new()
var empty = Shortcut.new()
f.load(path)
_ctrls.run_all.set_shortcut(f.get_value('main', 'run_all', empty))
_ctrls.run_current_script.set_shortcut(f.get_value('main', 'run_current_script', empty))
_ctrls.run_current_inner.set_shortcut(f.get_value('main', 'run_current_inner', empty))
_ctrls.run_current_test.set_shortcut(f.get_value('main', 'run_current_test', empty))
_ctrls.panel_button.set_shortcut(f.get_value('main', 'panel_button', empty))

View file

@ -1 +0,0 @@
uid://dsmt384dwo7em

View file

@ -1,153 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://bsk32dh41b4gs"]
[ext_resource type="PackedScene" uid="uid://sfb1fw8j6ufu" path="res://addons/gut/gui/ShortcutButton.tscn" id="1"]
[ext_resource type="Script" path="res://addons/gut/gui/BottomPanelShortcuts.gd" id="2"]
[node name="BottomPanelShortcuts" type="Popup"]
title = "Shortcuts"
size = Vector2i(500, 350)
visible = true
exclusive = true
unresizable = false
borderless = false
script = ExtResource("2")
[node name="Layout" type="VBoxContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 5.0
offset_right = -5.0
offset_bottom = 2.0
[node name="TopPad" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
[node name="Label2" type="Label" parent="Layout"]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
text = "Always Active"
[node name="ColorRect" type="ColorRect" parent="Layout/Label2"]
show_behind_parent = true
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0, 0, 0, 0.196078)
[node name="CPanelButton" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="Label" type="Label" parent="Layout/CPanelButton"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Show/Hide GUT Panel"
[node name="ShortcutButton" parent="Layout/CPanelButton" instance=ExtResource("1")]
layout_mode = 2
size_flags_horizontal = 3
[node name="GutPanelPad" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
[node name="Label" type="Label" parent="Layout"]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
text = "Only Active When GUT Panel Shown"
[node name="ColorRect2" type="ColorRect" parent="Layout/Label"]
show_behind_parent = true
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0, 0, 0, 0.196078)
[node name="TopPad2" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
[node name="CRunAll" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="Label" type="Label" parent="Layout/CRunAll"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run All"
[node name="ShortcutButton" parent="Layout/CRunAll" instance=ExtResource("1")]
layout_mode = 2
size_flags_horizontal = 3
[node name="CRunCurrentScript" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="Label" type="Label" parent="Layout/CRunCurrentScript"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run Current Script"
[node name="ShortcutButton" parent="Layout/CRunCurrentScript" instance=ExtResource("1")]
layout_mode = 2
size_flags_horizontal = 3
[node name="CRunCurrentInner" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="Label" type="Label" parent="Layout/CRunCurrentInner"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run Current Inner Class"
[node name="ShortcutButton" parent="Layout/CRunCurrentInner" instance=ExtResource("1")]
layout_mode = 2
size_flags_horizontal = 3
[node name="CRunCurrentTest" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="Label" type="Label" parent="Layout/CRunCurrentTest"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run Current Test"
[node name="ShortcutButton" parent="Layout/CRunCurrentTest" instance=ExtResource("1")]
layout_mode = 2
size_flags_horizontal = 3
[node name="CenterContainer2" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="ShiftDisclaimer" type="Label" parent="Layout"]
layout_mode = 2
text = "\"Shift\" cannot be the only modifier for a shortcut."
[node name="HBoxContainer" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="CenterContainer" type="CenterContainer" parent="Layout/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Hide" type="Button" parent="Layout/HBoxContainer"]
custom_minimum_size = Vector2(60, 30)
layout_mode = 2
text = "Close"
[node name="BottomPad" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(0, 10)
layout_mode = 2
size_flags_horizontal = 3
[connection signal="popup_hide" from="." to="." method="_on_popup_hide"]
[connection signal="pressed" from="Layout/HBoxContainer/Hide" to="." method="_on_Hide_pressed"]

View file

@ -0,0 +1,13 @@
[gd_resource type="Theme" format=3 uid="uid://dssgvu257o1si"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_u716c"]
bg_color = Color(0.43137255, 0.8784314, 0.6156863, 0.5254902)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ht2pf"]
bg_color = Color(0, 0.44705883, 0.23921569, 1)
[resource]
Button/colors/font_hover_pressed_color = Color(1, 1, 1, 1)
Button/colors/font_pressed_color = Color(1, 1, 1, 1)
Button/styles/hover = SubResource("StyleBoxFlat_u716c")
Button/styles/pressed = SubResource("StyleBoxFlat_ht2pf")

View file

@ -2,53 +2,75 @@
extends Control
var GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')
var TestScript = load('res://addons/gut/test.gd')
var GutConfigGui = load('res://addons/gut/gui/gut_config_gui.gd')
var ScriptTextEditors = load('res://addons/gut/gui/script_text_editor_controls.gd')
var AboutWindow = load("res://addons/gut/gui/about.tscn")
var _interface = null;
var _is_running = false;
var _is_running = false :
set(val):
_is_running = val
_disable_run_buttons(_is_running)
var _gut_config = load('res://addons/gut/gut_config.gd').new()
var _gut_config_gui = null
var _gut_plugin = null
var _light_color = Color(0, 0, 0, .5)
var _light_color = Color(0, 0, 0, .5) :
set(val):
_light_color = val
if(is_inside_tree()):
_ctrls.light.queue_redraw()
var _panel_button = null
var _last_selected_path = null
var _user_prefs = null
var _shell_out_panel = null
var menu_manager = null :
set(val):
menu_manager = val
if(val != null):
_apply_shortcuts()
menu_manager.toggle_windowed.connect(_on_toggle_windowed)
menu_manager.about.connect(show_about)
menu_manager.run_all.connect(_run_all)
menu_manager.show_gut.connect(_on_show_gut)
@onready var _ctrls = {
output = $layout/RSplit/CResults/TabBar/OutputText.get_rich_text_edit(),
output_ctrl = $layout/RSplit/CResults/TabBar/OutputText,
about = %ExtraButtons/About,
light = %StatusIndicator,
output_button = %ExtraButtons/OutputBtn,
run_button = $layout/ControlBar/RunAll,
shortcuts_button = $layout/ControlBar/Shortcuts,
settings_button = $layout/ControlBar/Settings,
run_results_button = $layout/ControlBar/RunResultsBtn,
output_button = $layout/ControlBar/OutputBtn,
settings = $layout/RSplit/sc/Settings,
shortcut_dialog = $BottomPanelShortcuts,
light = $layout/RSplit/CResults/ControlBar/Light3D,
results = {
bar = $layout/RSplit/CResults/ControlBar,
passing = $layout/RSplit/CResults/ControlBar/Passing/value,
failing = $layout/RSplit/CResults/ControlBar/Failing/value,
pending = $layout/RSplit/CResults/ControlBar/Pending/value,
errors = $layout/RSplit/CResults/ControlBar/Errors/value,
warnings = $layout/RSplit/CResults/ControlBar/Warnings/value,
orphans = $layout/RSplit/CResults/ControlBar/Orphans/value
},
run_externally_dialog = $ShellOutOptions,
run_mode = %ExtraButtons/RunMode,
run_at_cursor = $layout/ControlBar/RunAtCursor,
run_results = $layout/RSplit/CResults/TabBar/RunResults
run_results_button = %ExtraButtons/RunResultsBtn,
settings = $layout/RSplit/sc/Settings,
settings_button = %ExtraButtons/Settings,
shortcut_dialog = $ShortcutDialog,
shortcuts_button = %ExtraButtons/Shortcuts,
results = {
bar = $layout/ControlBar2,
errors = %errors_value,
failing = %failing_value,
orphans = %orphans_value,
passing = %passing_value,
pending = %pending_value,
warnings = %warnings_value,
},
}
func _init():
pass
@onready var results_v_split = %VSplitResults
@onready var results_h_split = %HSplitResults
@onready var results_tree = %RunResults
@onready var results_text = %OutputText
@onready var make_floating_btn = %MakeFloating
func _ready():
if(get_parent() is SubViewport):
return
GutEditorGlobals.create_temp_directory()
_user_prefs = GutEditorGlobals.user_prefs
@ -59,45 +81,75 @@ func _ready():
_gut_config.load_options(GutEditorGlobals.editor_run_gut_config_path)
_gut_config_gui.set_options(_gut_config.options)
_apply_options_to_controls()
_ctrls.shortcuts_button.icon = get_theme_icon('Shortcut', 'EditorIcons')
_ctrls.settings_button.icon = get_theme_icon('Tools', 'EditorIcons')
_ctrls.run_results_button.icon = get_theme_icon('AnimationTrackGroup', 'EditorIcons') # Tree
_ctrls.output_button.icon = get_theme_icon('Font', 'EditorIcons')
make_floating_btn.icon = get_theme_icon("MakeFloating", 'EditorIcons')
make_floating_btn.text = ''
_ctrls.about.icon = get_theme_icon('Info', 'EditorIcons')
_ctrls.about.text = ''
_ctrls.run_mode.icon = get_theme_icon("ViewportSpeed", 'EditorIcons')
_ctrls.run_results.set_output_control(_ctrls.output_ctrl)
results_tree.set_output_control(results_text)
var check_import = load('res://addons/gut/images/red.png')
var check_import = load('res://addons/gut/images/HSplitContainer.svg')
if(check_import == null):
_ctrls.run_results.add_centered_text("GUT got some new images that are not imported yet. Please restart Godot.")
results_tree.add_centered_text("GUT got some new images that are not imported yet. Please restart Godot.")
print('GUT got some new images that are not imported yet. Please restart Godot.')
else:
_ctrls.run_results.add_centered_text("Let's run some tests!")
results_tree.add_centered_text("Let's run some tests!")
_ctrls.run_externally_dialog.load_from_file()
_apply_options_to_controls()
results_vert_layout()
func _apply_options_to_controls():
hide_settings(_user_prefs.hide_settings.value)
hide_result_tree(_user_prefs.hide_result_tree.value)
hide_output_text(_user_prefs.hide_output_text.value)
_ctrls.run_results.set_show_orphans(!_gut_config.options.hide_orphans)
func _process(delta):
func _process(_delta):
if(_is_running):
if(!_interface.is_playing_scene()):
if(_ctrls.run_externally_dialog.should_run_externally()):
if(!is_instance_valid(_shell_out_panel)):
_is_running = false
show_me()
elif(!_interface.is_playing_scene()):
_is_running = false
_ctrls.output_ctrl.add_text("\ndone")
results_text.add_text("\ndone")
load_result_output()
_gut_plugin.make_bottom_panel_item_visible(self)
show_me()
# ---------------
# Private
# ---------------
func _apply_options_to_controls():
hide_settings(_user_prefs.hide_settings.value)
hide_result_tree(_user_prefs.hide_result_tree.value)
hide_output_text(_user_prefs.hide_output_text.value)
results_tree.set_show_orphans(!_gut_config.options.hide_orphans)
var shell_dialog_size = _user_prefs.run_externally_options_dialog_size.value
func load_shortcuts():
_ctrls.shortcut_dialog.load_shortcuts()
_apply_shortcuts()
if(shell_dialog_size != Vector2i(-1, -1)):
_ctrls.run_externally_dialog.size = Vector2i(shell_dialog_size)
if(_user_prefs.shortcuts_dialog_size.value != Vector2i(-1, -1)):
_ctrls.shortcut_dialog.size = _user_prefs.shortcuts_dialog_size.value
var mode_ind = 'Ed'
if(_ctrls.run_externally_dialog.run_mode == _ctrls.run_externally_dialog.RUN_MODE_BLOCKING):
mode_ind = 'ExB'
elif(_ctrls.run_externally_dialog.run_mode == _ctrls.run_externally_dialog.RUN_MODE_NON_BLOCKING):
mode_ind = 'ExN'
_ctrls.run_mode.text = mode_ind
_ctrls.run_at_cursor.apply_gut_config(_gut_config)
func _disable_run_buttons(should):
_ctrls.run_button.disabled = should
_ctrls.run_at_cursor.disabled = should
func _is_test_script(script):
@ -109,22 +161,31 @@ func _is_test_script(script):
func _show_errors(errs):
_ctrls.output_ctrl.clear()
results_text.clear()
var text = "Cannot run tests, you have a configuration error:\n"
for e in errs:
text += str('* ', e, "\n")
text += "Check your settings ----->"
_ctrls.output_ctrl.add_text(text)
results_text.add_text(text)
hide_output_text(false)
hide_settings(false)
func _save_config():
func _save_user_prefs():
_user_prefs.hide_settings.value = !_ctrls.settings_button.button_pressed
_user_prefs.hide_result_tree.value = !_ctrls.run_results_button.button_pressed
_user_prefs.hide_output_text.value = !_ctrls.output_button.button_pressed
_user_prefs.shortcuts_dialog_size.value = _ctrls.shortcut_dialog.size
_user_prefs.run_externally.value = _ctrls.run_externally_dialog.run_mode != _ctrls.run_externally_dialog.RUN_MODE_EDITOR
_user_prefs.run_externally_options_dialog_size.value = _ctrls.run_externally_dialog.size
_user_prefs.save_it()
func _save_config():
_save_user_prefs()
_gut_config.options = _gut_config_gui.get_options(_gut_config.options)
var w_result = _gut_config.write_options(GutEditorGlobals.editor_run_gut_config_path)
if(w_result != OK):
@ -133,8 +194,25 @@ func _save_config():
_gut_config_gui.mark_saved()
func _run_externally():
_shell_out_panel = GutUtils.RunExternallyScene.instantiate()
_shell_out_panel.bottom_panel = self
_shell_out_panel.blocking_mode = _ctrls.run_externally_dialog.run_mode
_shell_out_panel.additional_arguments = _ctrls.run_externally_dialog.get_additional_arguments_array()
add_child(_shell_out_panel)
_shell_out_panel.run_tests()
func _run_tests():
show_me()
if(_is_running):
push_error("GUT: Cannot run tests, tests are already running.")
return
clear_results()
GutEditorGlobals.create_temp_directory()
_light_color = Color.BLUE
var issues = _gut_config_gui.get_config_issues()
if(issues.size() > 0):
@ -142,29 +220,43 @@ func _run_tests():
return
write_file(GutEditorGlobals.editor_run_bbcode_results_path, 'Run in progress')
write_file(GutEditorGlobals.editor_run_json_results_path, '')
_save_config()
_apply_options_to_controls()
_ctrls.output_ctrl.clear()
_ctrls.run_results.clear()
_ctrls.run_results.add_centered_text('Running...')
results_text.clear()
results_tree.clear()
results_tree.add_centered_text('Running...')
_interface.play_custom_scene('res://addons/gut/gui/run_from_editor.tscn')
_is_running = true
_ctrls.output_ctrl.add_text('Running...')
results_text.add_text('Running...')
if(_ctrls.run_externally_dialog.should_run_externally()):
_gut_plugin.make_bottom_panel_item_visible(self)
_run_externally()
else:
_interface.play_custom_scene('res://addons/gut/gui/run_from_editor.tscn')
func _apply_shortcuts():
_ctrls.run_button.shortcut = _ctrls.shortcut_dialog.get_run_all()
if(menu_manager != null):
menu_manager.apply_gut_shortcuts(_ctrls.shortcut_dialog)
_ctrls.run_button.shortcut = \
_ctrls.shortcut_dialog.scbtn_run_all.get_shortcut()
_ctrls.run_at_cursor.get_script_button().shortcut = \
_ctrls.shortcut_dialog.get_run_current_script()
_ctrls.shortcut_dialog.scbtn_run_current_script.get_shortcut()
_ctrls.run_at_cursor.get_inner_button().shortcut = \
_ctrls.shortcut_dialog.get_run_current_inner()
_ctrls.shortcut_dialog.scbtn_run_current_inner.get_shortcut()
_ctrls.run_at_cursor.get_test_button().shortcut = \
_ctrls.shortcut_dialog.get_run_current_test()
_panel_button.shortcut = _ctrls.shortcut_dialog.get_panel_button()
_ctrls.shortcut_dialog.scbtn_run_current_test.get_shortcut()
# Took this out because it seems to break using the shortcut when docked.
# Though it does allow the shortcut to work when windowed. Shortcuts
# are weird.
# make_floating_btn.shortcut = \
# _ctrls.shortcut_dialog.scbtn_windowed.get_shortcut()
if(get_parent() is EditorDock):
get_parent().dock_shortcut = _ctrls.shortcut_dialog.scbtn_panel.get_shortcut()
func _run_all():
@ -187,11 +279,6 @@ func _on_Light_draw():
l.draw_circle(Vector2(l.size.x / 2, l.size.y / 2), l.size.x / 2, _light_color)
func _on_editor_script_changed(script):
if(script):
set_current_script(script)
func _on_RunAll_pressed():
_run_all()
@ -199,14 +286,17 @@ func _on_RunAll_pressed():
func _on_Shortcuts_pressed():
_ctrls.shortcut_dialog.popup_centered()
func _on_bottom_panel_shortcuts_visibility_changed():
func _on_sortcut_dialog_confirmed() -> void:
_apply_shortcuts()
_ctrls.shortcut_dialog.save_shortcuts()
_save_user_prefs()
func _on_RunAtCursor_run_tests(what):
_gut_config.options.selected = what.script
_gut_config.options.inner_class = what.inner_class
_gut_config.options.unit_test_name = what.test_method
_gut_config.options.unit_test_name = what.method
_run_tests()
@ -231,11 +321,51 @@ func _on_RunResultsBtn_pressed():
func _on_UseColors_pressed():
pass
func _on_shell_out_options_confirmed() -> void:
_ctrls.run_externally_dialog.save_to_file()
_save_user_prefs()
_apply_options_to_controls()
func _on_run_mode_pressed() -> void:
_ctrls.run_externally_dialog.popup_centered()
func _on_toggle_windowed():
_gut_plugin.toggle_windowed()
func _on_to_window_pressed() -> void:
_gut_plugin.toggle_windowed()
func _on_show_gut() -> void:
show_hide()
func _on_about_pressed() -> void:
show_about()
func _on_horiz_layout_pressed() -> void:
results_horiz_layout()
func _on_vert_layout_pressed() -> void:
results_vert_layout()
# ---------------
# Public
# ---------------
func load_shortcuts():
_ctrls.shortcut_dialog.load_shortcuts()
_apply_shortcuts()
func hide_result_tree(should):
_ctrls.run_results.visible = !should
results_tree.visible = !should
_ctrls.run_results_button.button_pressed = !should
@ -255,69 +385,84 @@ func hide_settings(should):
func hide_output_text(should):
$layout/RSplit/CResults/TabBar/OutputText.visible = !should
results_text.visible = !should
_ctrls.output_button.button_pressed = !should
func load_result_output():
_ctrls.output_ctrl.load_file(GutEditorGlobals.editor_run_bbcode_results_path)
func clear_results():
_light_color = Color(0, 0, 0, .5)
_ctrls.results.passing.text = "0"
_ctrls.results.passing.get_parent().visible = false
_ctrls.results.failing.text = "0"
_ctrls.results.failing.get_parent().visible = false
_ctrls.results.pending.text = "0"
_ctrls.results.pending.get_parent().visible = false
_ctrls.results.errors.text = "0"
_ctrls.results.errors.get_parent().visible = false
_ctrls.results.warnings.text = "0"
_ctrls.results.warnings.get_parent().visible = false
_ctrls.results.orphans.text = "0"
_ctrls.results.orphans.get_parent().visible = false
func load_result_json():
var summary = get_file_as_text(GutEditorGlobals.editor_run_json_results_path)
var test_json_conv = JSON.new()
if (test_json_conv.parse(summary) != OK):
return
var results = test_json_conv.get_data()
_ctrls.run_results.load_json_results(results)
results_tree.load_json_results(results)
var summary_json = results['test_scripts']['props']
_ctrls.results.passing.text = str(summary_json.passing)
_ctrls.results.passing.text = str(int(summary_json.passing))
_ctrls.results.passing.get_parent().visible = true
_ctrls.results.failing.text = str(summary_json.failures)
_ctrls.results.failing.text = str(int(summary_json.failures))
_ctrls.results.failing.get_parent().visible = true
_ctrls.results.pending.text = str(summary_json.pending)
_ctrls.results.pending.text = str(int(summary_json.pending) + int(summary_json.risky))
_ctrls.results.pending.get_parent().visible = _ctrls.results.pending.text != '0'
_ctrls.results.errors.text = str(summary_json.errors)
_ctrls.results.errors.text = str(int(summary_json.errors))
_ctrls.results.errors.get_parent().visible = _ctrls.results.errors.text != '0'
_ctrls.results.warnings.text = str(summary_json.warnings)
_ctrls.results.warnings.text = str(int(summary_json.warnings))
_ctrls.results.warnings.get_parent().visible = _ctrls.results.warnings.text != '0'
_ctrls.results.orphans.text = str(summary_json.orphans)
_ctrls.results.orphans.text = str(int(summary_json.orphans))
_ctrls.results.orphans.get_parent().visible = _ctrls.results.orphans.text != '0' and !_gut_config.options.hide_orphans
if(summary_json.tests == 0):
_light_color = Color(1, 0, 0, .75)
elif(summary_json.failures != 0):
_light_color = Color(1, 0, 0, .75)
elif(summary_json.pending != 0):
elif(summary_json.pending != 0 or summary_json.risky != 0):
_light_color = Color(1, 1, 0, .75)
else:
_light_color = Color(0, 1, 0, .75)
_ctrls.light.visible = true
_ctrls.light.queue_redraw()
func set_current_script(script):
if(script):
if(_is_test_script(script)):
var file = script.resource_path.get_file()
_last_selected_path = script.resource_path.get_file()
_ctrls.run_at_cursor.activate_for_script(script.resource_path)
func load_result_text():
results_text.load_file(GutEditorGlobals.editor_run_bbcode_results_path)
func load_result_output():
load_result_text()
load_result_json()
func set_interface(value):
_interface = value
_interface.get_script_editor().connect("editor_script_changed",Callable(self,'_on_editor_script_changed'))
var ste = ScriptTextEditors.new(_interface.get_script_editor())
_ctrls.run_results.set_interface(_interface)
_ctrls.run_results.set_script_text_editors(ste)
_ctrls.run_at_cursor.set_script_text_editors(ste)
set_current_script(_interface.get_script_editor().get_current_script())
results_tree.set_interface(_interface)
func set_plugin(value):
@ -327,9 +472,7 @@ func set_plugin(value):
func set_panel_button(value):
_panel_button = value
# ------------------------------------------------------------------------------
# Write a file.
# ------------------------------------------------------------------------------
func write_file(path, content):
var f = FileAccess.open(path, FileAccess.WRITE)
if(f != null):
@ -339,9 +482,6 @@ func write_file(path, content):
return FileAccess.get_open_error()
# ------------------------------------------------------------------------------
# Returns the text of a file or an empty string if the file could not be opened.
# ------------------------------------------------------------------------------
func get_file_as_text(path):
var to_return = ''
var f = FileAccess.open(path, FileAccess.READ)
@ -351,11 +491,66 @@ func get_file_as_text(path):
return to_return
# ------------------------------------------------------------------------------
# return if_null if value is null otherwise return value
# ------------------------------------------------------------------------------
func nvl(value, if_null):
if(value == null):
return if_null
func get_text_output_control():
return results_text
func add_output_text(text):
results_text.add_text(text)
func show_about():
var about = AboutWindow.instantiate()
add_child(about)
about.popup_centered()
about.confirmed.connect(about.queue_free)
func show_me():
get_parent().make_visible()
func show_hide():
if(owner is Window):
if(owner.has_focus()):
var win_to_focus_on = EditorInterface.get_editor_main_screen().get_parent()
while(win_to_focus_on != null and win_to_focus_on is not Window):
win_to_focus_on = win_to_focus_on.get_parent()
if(win_to_focus_on != null):
win_to_focus_on.grab_focus()
else:
owner.grab_focus()
else:
return value
pass
# We don't have to do anything when we are docked because the GUT
# bottom panel has the shortcut and it does the toggling all on its
# own.
func get_shortcut_dialog():
return _ctrls.shortcut_dialog
func results_vert_layout():
if(results_tree.get_parent() != results_v_split):
results_tree.reparent(results_v_split)
results_text.reparent(results_v_split)
results_v_split.visible = true
results_h_split.visible = false
func results_horiz_layout():
if(results_tree.get_parent() != results_h_split):
results_tree.reparent(results_h_split)
results_text.reparent(results_h_split)
results_v_split.visible = false
results_h_split.visible = true
func show_layout_buttons(should):
%HorizLayout.visible = should
%VertLayout.visible = should
func get_panel_shortcut():
_ctrls.shortcut_dialog.scbtn_panel.get_shortcut()

View file

@ -1 +1 @@
uid://b84o6yt8c0i3p
uid://dtvnb0xatk0my

View file

@ -1,27 +1,21 @@
[gd_scene load_steps=10 format=3 uid="uid://b3bostcslstem"]
[gd_scene format=3 uid="uid://b3bostcslstem"]
[ext_resource type="Script" path="res://addons/gut/gui/GutBottomPanel.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://bsk32dh41b4gs" path="res://addons/gut/gui/BottomPanelShortcuts.tscn" id="2"]
[ext_resource type="Script" uid="uid://dtvnb0xatk0my" path="res://addons/gut/gui/GutBottomPanel.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://0yunjxtaa8iw" path="res://addons/gut/gui/RunAtCursor.tscn" id="3"]
[ext_resource type="Texture2D" uid="uid://cr6tvdv0ve6cv" path="res://addons/gut/gui/play.png" id="4"]
[ext_resource type="Texture2D" uid="uid://bvo0uao7deu0q" path="res://addons/gut/icon.png" id="4_xv2r3"]
[ext_resource type="PackedScene" uid="uid://4gyyn12um08h" path="res://addons/gut/gui/RunResults.tscn" id="5"]
[ext_resource type="Texture2D" uid="uid://ljc2viafngwd" path="res://addons/gut/images/HSplitContainer.svg" id="5_qdqpf"]
[ext_resource type="PackedScene" uid="uid://bqmo4dj64c7yl" path="res://addons/gut/gui/OutputText.tscn" id="6"]
[ext_resource type="Texture2D" uid="uid://bhew20crsywxr" path="res://addons/gut/images/VSplitContainer.svg" id="6_ot46d"]
[ext_resource type="PackedScene" uid="uid://dj5ve0bq7xa5j" path="res://addons/gut/gui/ShortcutDialog.tscn" id="7_srqj5"]
[ext_resource type="PackedScene" uid="uid://ckv5eh8xyrwbk" path="res://addons/gut/gui/ShellOutOptions.tscn" id="7_xv2r3"]
[sub_resource type="Shortcut" id="9"]
[sub_resource type="Image" id="Image_4maas"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ButtonGroup" id="ButtonGroup_bskl7"]
[sub_resource type="ImageTexture" id="ImageTexture_umaha"]
image = SubResource("Image_4maas")
[node name="GutBottomPanel" type="Control"]
[node name="GutBottomPanel" type="Control" unique_id=1136630421]
custom_minimum_size = Vector2(250, 250)
layout_mode = 3
anchor_left = -0.0025866
@ -34,217 +28,292 @@ offset_right = 2.64862
offset_bottom = 1.05945
script = ExtResource("1")
[node name="layout" type="VBoxContainer" parent="."]
[node name="layout" type="VBoxContainer" parent="." unique_id=790416054]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="ControlBar" type="HBoxContainer" parent="layout"]
[node name="ControlBar" type="HBoxContainer" parent="layout" unique_id=672788595]
layout_mode = 2
[node name="RunAll" type="Button" parent="layout/ControlBar"]
[node name="RunAll" type="Button" parent="layout/ControlBar" unique_id=678231332]
layout_mode = 2
size_flags_vertical = 11
shortcut = SubResource("9")
text = "Run All"
icon = ExtResource("4")
[node name="Label" type="Label" parent="layout/ControlBar"]
layout_mode = 2
mouse_filter = 1
text = "Current: "
[node name="RunAtCursor" parent="layout/ControlBar" instance=ExtResource("3")]
[node name="Sep3" type="ColorRect" parent="layout/ControlBar" unique_id=1600176902]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="CenterContainer2" type="CenterContainer" parent="layout/ControlBar"]
[node name="RunAtCursor" parent="layout/ControlBar" unique_id=1603612118 instance=ExtResource("3")]
custom_minimum_size = Vector2(237, 0)
layout_mode = 2
[node name="CenterContainer2" type="CenterContainer" parent="layout/ControlBar" unique_id=269691286]
layout_mode = 2
size_flags_horizontal = 3
[node name="Sep1" type="ColorRect" parent="layout/ControlBar"]
[node name="MakeFloating" type="Button" parent="layout/ControlBar" unique_id=1273743620]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Move the GUT panel to a window."
icon = ExtResource("4_xv2r3")
flat = true
[node name="HorizLayout" type="Button" parent="layout/ControlBar" unique_id=679134345]
unique_name_in_owner = true
texture_filter = 1
layout_mode = 2
toggle_mode = true
button_pressed = true
button_group = SubResource("ButtonGroup_bskl7")
icon = ExtResource("5_qdqpf")
icon_alignment = 1
[node name="VertLayout" type="Button" parent="layout/ControlBar" unique_id=1762257478]
unique_name_in_owner = true
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_bskl7")
icon = ExtResource("6_ot46d")
[node name="ControlBar2" type="HBoxContainer" parent="layout" unique_id=920578685]
layout_mode = 2
[node name="Sep2" type="ColorRect" parent="layout/ControlBar2" unique_id=781273927]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
color = Color(1, 1, 1, 0)
[node name="StatusIndicator" type="Control" parent="layout/ControlBar2" unique_id=173224142]
unique_name_in_owner = true
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
[node name="Passing" type="HBoxContainer" parent="layout/ControlBar2" unique_id=2110519605]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/ControlBar2/Passing" unique_id=164849935]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="RunResultsBtn" type="Button" parent="layout/ControlBar"]
[node name="label" type="Label" parent="layout/ControlBar2/Passing" unique_id=1437040616]
layout_mode = 2
toggle_mode = true
icon = SubResource("ImageTexture_umaha")
text = "Pass"
[node name="OutputBtn" type="Button" parent="layout/ControlBar"]
[node name="passing_value" type="Label" parent="layout/ControlBar2/Passing" unique_id=551548364]
unique_name_in_owner = true
layout_mode = 2
toggle_mode = true
icon = SubResource("ImageTexture_umaha")
text = "---"
[node name="Settings" type="Button" parent="layout/ControlBar"]
[node name="Failing" type="HBoxContainer" parent="layout/ControlBar2" unique_id=1824266070]
visible = false
layout_mode = 2
toggle_mode = true
icon = SubResource("ImageTexture_umaha")
[node name="Sep2" type="ColorRect" parent="layout/ControlBar"]
[node name="Sep" type="ColorRect" parent="layout/ControlBar2/Failing" unique_id=1239759559]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="Shortcuts" type="Button" parent="layout/ControlBar"]
[node name="label" type="Label" parent="layout/ControlBar2/Failing" unique_id=1956836855]
layout_mode = 2
text = "Fail"
[node name="failing_value" type="Label" parent="layout/ControlBar2/Failing" unique_id=636519064]
unique_name_in_owner = true
layout_mode = 2
text = "---"
[node name="Pending" type="HBoxContainer" parent="layout/ControlBar2" unique_id=1121510368]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/ControlBar2/Pending" unique_id=896038473]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/ControlBar2/Pending" unique_id=1642849369]
layout_mode = 2
text = "Risky"
[node name="pending_value" type="Label" parent="layout/ControlBar2/Pending" unique_id=162567404]
unique_name_in_owner = true
layout_mode = 2
text = "---"
[node name="Orphans" type="HBoxContainer" parent="layout/ControlBar2" unique_id=2040966734]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/ControlBar2/Orphans" unique_id=153606264]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/ControlBar2/Orphans" unique_id=1507291727]
layout_mode = 2
text = "Orphans"
[node name="orphans_value" type="Label" parent="layout/ControlBar2/Orphans" unique_id=235887326]
unique_name_in_owner = true
layout_mode = 2
text = "---"
[node name="Errors" type="HBoxContainer" parent="layout/ControlBar2" unique_id=1697483051]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/ControlBar2/Errors" unique_id=411797244]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/ControlBar2/Errors" unique_id=299959536]
layout_mode = 2
text = "Errors"
[node name="errors_value" type="Label" parent="layout/ControlBar2/Errors" unique_id=1340004517]
unique_name_in_owner = true
layout_mode = 2
text = "---"
[node name="Warnings" type="HBoxContainer" parent="layout/ControlBar2" unique_id=1097631959]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/ControlBar2/Warnings" unique_id=763793318]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/ControlBar2/Warnings" unique_id=272711725]
layout_mode = 2
text = "Warnings"
[node name="warnings_value" type="Label" parent="layout/ControlBar2/Warnings" unique_id=868414665]
unique_name_in_owner = true
layout_mode = 2
text = "---"
[node name="CenterContainer" type="CenterContainer" parent="layout/ControlBar2" unique_id=1705687195]
layout_mode = 2
size_flags_horizontal = 3
[node name="ExtraButtons" type="HBoxContainer" parent="layout/ControlBar2" unique_id=1132817721]
unique_name_in_owner = true
layout_mode = 2
[node name="Sep1" type="ColorRect" parent="layout/ControlBar2/ExtraButtons" unique_id=601143834]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="RunMode" type="Button" parent="layout/ControlBar2/ExtraButtons" unique_id=1469737845]
layout_mode = 2
tooltip_text = "Run Mode. Run tests through the editor or externally in a different process."
text = "ExN"
icon = ExtResource("4_xv2r3")
[node name="Sep2" type="ColorRect" parent="layout/ControlBar2/ExtraButtons" unique_id=710471274]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="RunResultsBtn" type="Button" parent="layout/ControlBar2/ExtraButtons" unique_id=385339391]
layout_mode = 2
tooltip_text = "Show/Hide Result Tree"
toggle_mode = true
button_pressed = true
icon = ExtResource("4_xv2r3")
[node name="OutputBtn" type="Button" parent="layout/ControlBar2/ExtraButtons" unique_id=1461861149]
layout_mode = 2
tooltip_text = "Show/Hide Text Output"
toggle_mode = true
button_pressed = true
icon = ExtResource("4_xv2r3")
[node name="Settings" type="Button" parent="layout/ControlBar2/ExtraButtons" unique_id=1063789901]
layout_mode = 2
tooltip_text = "GUT Settings"
toggle_mode = true
button_pressed = true
icon = ExtResource("4_xv2r3")
[node name="Sep3" type="ColorRect" parent="layout/ControlBar2/ExtraButtons" unique_id=650070116]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="Shortcuts" type="Button" parent="layout/ControlBar2/ExtraButtons" unique_id=545241830]
layout_mode = 2
size_flags_vertical = 11
icon = SubResource("ImageTexture_umaha")
tooltip_text = "GUT Shortcuts"
icon = ExtResource("4_xv2r3")
[node name="RSplit" type="HSplitContainer" parent="layout"]
[node name="About" type="Button" parent="layout/ControlBar2/ExtraButtons" unique_id=1763800911]
layout_mode = 2
tooltip_text = "About"
text = "A"
[node name="RSplit" type="HSplitContainer" parent="layout" unique_id=153697410]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
collapsed = true
[node name="sc" type="ScrollContainer" parent="layout/RSplit"]
[node name="CResults" type="VBoxContainer" parent="layout/RSplit" unique_id=124140650]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="HSplitResults" type="HSplitContainer" parent="layout/RSplit/CResults" unique_id=241208440]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="RunResults" parent="layout/RSplit/CResults/HSplitResults" unique_id=1277406275 instance=ExtResource("5")]
unique_name_in_owner = true
custom_minimum_size = Vector2(255, 255)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="OutputText" parent="layout/RSplit/CResults/HSplitResults" unique_id=1658148044 instance=ExtResource("6")]
unique_name_in_owner = true
layout_mode = 2
[node name="VSplitResults" type="VSplitContainer" parent="layout/RSplit/CResults" unique_id=394710781]
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_vertical = 3
[node name="sc" type="ScrollContainer" parent="layout/RSplit" unique_id=978940325]
custom_minimum_size = Vector2(500, 2.08165e-12)
layout_mode = 2
size_flags_vertical = 3
[node name="Settings" type="VBoxContainer" parent="layout/RSplit/sc"]
[node name="Settings" type="VBoxContainer" parent="layout/RSplit/sc" unique_id=1724913756]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="CResults" type="VBoxContainer" parent="layout/RSplit"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="ControlBar" type="HBoxContainer" parent="layout/RSplit/CResults"]
layout_mode = 2
[node name="Sep2" type="ColorRect" parent="layout/RSplit/CResults/ControlBar"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="Light3D" type="Control" parent="layout/RSplit/CResults/ControlBar"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
[node name="Passing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
[node name="ShortcutDialog" parent="." unique_id=1224532527 instance=ExtResource("7_srqj5")]
size = Vector2i(1512, 1571)
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Passing"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"]
layout_mode = 2
text = "Passing"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"]
layout_mode = 2
text = "---"
[node name="Failing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Failing"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"]
layout_mode = 2
text = "Failing"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"]
layout_mode = 2
text = "---"
[node name="Pending" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Pending"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"]
layout_mode = 2
text = "Pending"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"]
layout_mode = 2
text = "---"
[node name="Orphans" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Orphans"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"]
layout_mode = 2
text = "Orphans"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"]
layout_mode = 2
text = "---"
[node name="Errors" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Errors"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"]
layout_mode = 2
text = "Errors"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"]
layout_mode = 2
text = "---"
[node name="Warnings" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Warnings"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"]
layout_mode = 2
text = "Warnings"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"]
layout_mode = 2
text = "---"
[node name="CenterContainer" type="CenterContainer" parent="layout/RSplit/CResults/ControlBar"]
layout_mode = 2
size_flags_horizontal = 3
[node name="TabBar" type="HSplitContainer" parent="layout/RSplit/CResults"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="RunResults" parent="layout/RSplit/CResults/TabBar" instance=ExtResource("5")]
visible = false
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="OutputText" parent="layout/RSplit/CResults/TabBar" instance=ExtResource("6")]
visible = false
layout_mode = 2
[node name="BottomPanelShortcuts" parent="." instance=ExtResource("2")]
[node name="ShellOutOptions" parent="." unique_id=1974075127 instance=ExtResource("7_xv2r3")]
size = Vector2i(1300, 1336)
visible = false
[connection signal="pressed" from="layout/ControlBar/RunAll" to="." method="_on_RunAll_pressed"]
[connection signal="run_tests" from="layout/ControlBar/RunAtCursor" to="." method="_on_RunAtCursor_run_tests"]
[connection signal="pressed" from="layout/ControlBar/RunResultsBtn" to="." method="_on_RunResultsBtn_pressed"]
[connection signal="pressed" from="layout/ControlBar/OutputBtn" to="." method="_on_OutputBtn_pressed"]
[connection signal="pressed" from="layout/ControlBar/Settings" to="." method="_on_Settings_pressed"]
[connection signal="pressed" from="layout/ControlBar/Shortcuts" to="." method="_on_Shortcuts_pressed"]
[connection signal="draw" from="layout/RSplit/CResults/ControlBar/Light3D" to="." method="_on_Light_draw"]
[connection signal="visibility_changed" from="BottomPanelShortcuts" to="." method="_on_bottom_panel_shortcuts_visibility_changed"]
[connection signal="pressed" from="layout/ControlBar/MakeFloating" to="." method="_on_to_window_pressed"]
[connection signal="pressed" from="layout/ControlBar/HorizLayout" to="." method="_on_horiz_layout_pressed"]
[connection signal="pressed" from="layout/ControlBar/VertLayout" to="." method="_on_vert_layout_pressed"]
[connection signal="draw" from="layout/ControlBar2/StatusIndicator" to="." method="_on_Light_draw"]
[connection signal="pressed" from="layout/ControlBar2/ExtraButtons/RunMode" to="." method="_on_run_mode_pressed"]
[connection signal="pressed" from="layout/ControlBar2/ExtraButtons/RunResultsBtn" to="." method="_on_RunResultsBtn_pressed"]
[connection signal="pressed" from="layout/ControlBar2/ExtraButtons/OutputBtn" to="." method="_on_OutputBtn_pressed"]
[connection signal="pressed" from="layout/ControlBar2/ExtraButtons/Settings" to="." method="_on_Settings_pressed"]
[connection signal="pressed" from="layout/ControlBar2/ExtraButtons/Shortcuts" to="." method="_on_Shortcuts_pressed"]
[connection signal="pressed" from="layout/ControlBar2/ExtraButtons/About" to="." method="_on_about_pressed"]
[connection signal="confirmed" from="ShortcutDialog" to="." method="_on_sortcut_dialog_confirmed"]
[connection signal="confirmed" from="ShellOutOptions" to="." method="_on_shell_out_options_confirmed"]

View file

@ -9,8 +9,7 @@ var GutConfigGui = load('res://addons/gut/gui/gut_config_gui.gd')
var _config = GutConfig.new()
var _config_gui = null
var _gut_runner = GutRunnerScene.instantiate()
var _has_connected = false
var _gut_runner = null
var _tree_root : TreeItem = null
var _script_icon = load('res://addons/gut/images/Script.svg')
@ -43,6 +42,7 @@ func _ready():
if Engine.is_editor_hint():
return
_gut_runner = GutRunnerScene.instantiate()
$Bg.color = bg_color
_ctrls.tabs.set_tab_title(0, 'Tests')
_ctrls.tabs.set_tab_title(1, 'Settings')
@ -260,6 +260,10 @@ func run_tests(options = null):
_config.options = _config_gui.get_options(_config.options)
else:
_config.options = options
# We ar running from within the game, so we should not exit, ever.
_config.options.should_exit_on_success = false
_config.options.should_exit = false
_gut_runner.get_gut().get_test_collector().clear()
_gut_runner.set_gut_config(_config)

View file

@ -1 +1 @@
uid://dxbu5m7ovktb8
uid://cqlvpwidawld6

View file

@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://4jb53yqktyfg"]
[ext_resource type="Script" path="res://addons/gut/gui/GutControl.gd" id="1_eprql"]
[ext_resource type="Script" uid="uid://cqlvpwidawld6" path="res://addons/gut/gui/GutControl.gd" id="1_eprql"]
[node name="GutControl" type="Control"]
layout_mode = 3

View file

@ -0,0 +1,36 @@
[gd_scene load_steps=4 format=3 uid="uid://bjkn8mhx2fmt1"]
[ext_resource type="Script" uid="uid://b8lvgepb64m8t" path="res://addons/gut/gui/gut_logo.gd" id="1_ba6lh"]
[ext_resource type="Texture2D" uid="uid://dyxbmyvpkkcvs" path="res://addons/gut/images/GutIconV2_base.png" id="2_ba6lh"]
[ext_resource type="Texture2D" uid="uid://dx0yxxn5q7doc" path="res://addons/gut/images/eyey.png" id="3_rc8fb"]
[node name="Logo" type="Node2D"]
script = ExtResource("1_ba6lh")
[node name="BaseLogo" type="Sprite2D" parent="."]
scale = Vector2(0.5, 0.5)
texture = ExtResource("2_ba6lh")
[node name="LeftEye" type="Sprite2D" parent="BaseLogo"]
visible = false
position = Vector2(-238, 16)
texture = ExtResource("3_rc8fb")
[node name="RightEye" type="Sprite2D" parent="BaseLogo"]
visible = false
position = Vector2(239, 16)
texture = ExtResource("3_rc8fb")
[node name="ResetTimer" type="Timer" parent="."]
wait_time = 5.0
one_shot = true
[node name="FaceButton" type="Button" parent="."]
modulate = Color(1, 1, 1, 0)
offset_left = -141.0
offset_top = -113.0
offset_right = 140.0
offset_bottom = 175.0
[connection signal="timeout" from="ResetTimer" to="." method="_on_reset_timer_timeout"]
[connection signal="pressed" from="FaceButton" to="." method="_on_face_button_pressed"]

View file

@ -28,12 +28,15 @@ var result_json_path = null
var lgr = GutUtils.get_logger()
var gut_config = null
var error_tracker = GutUtils.get_error_tracker()
var _hid_gut = null;
# Lazy loaded gut instance. Settable for testing purposes.
var gut = _hid_gut :
get:
if(_hid_gut == null):
_hid_gut = Gut.new()
_hid_gut = Gut.new(lgr)
_hid_gut.error_tracker = error_tracker
return _hid_gut
set(val):
_hid_gut = val
@ -94,7 +97,8 @@ func _write_results_for_gut_panel():
func _handle_quit(should_exit, should_exit_on_success, override_exit_code=EXIT_OK):
var quitting_time = should_exit or \
(should_exit_on_success and gut.get_fail_count() == 0)
(should_exit_on_success and gut.get_fail_count() == 0) or \
GutUtils.is_headless()
if(!quitting_time):
if(should_exit_on_success):
@ -122,6 +126,8 @@ func _end_run(override_exit_code=EXIT_OK):
if(_ran_from_editor):
_write_results_for_gut_panel()
GutErrorTracker.deregister_logger(error_tracker)
_handle_quit(gut_config.options.should_exit,
gut_config.options.should_exit_on_success,
override_exit_code)
@ -157,7 +163,9 @@ func run_tests(show_gui=true):
_setup_gui(show_gui)
if(gut_config.options.dirs.size() + gut_config.options.tests.size() == 0):
var err_text = "You do not have any directories configured, so GUT doesn't know where to find the tests. Tell GUT where to find the tests and GUT shall run the tests."
var err_text = "You do not have any directories configured, so GUT " + \
"doesn't know where to find the tests. Tell GUT where to find the " + \
"tests and GUT shall run the tests."
lgr.error(err_text)
push_error(err_text)
_end_run(EXIT_ERROR)
@ -178,11 +186,15 @@ func run_tests(show_gui=true):
else:
add_child(gut)
gut.end_run.connect(_on_tests_finished)
if(!gut.end_run.is_connected(_on_tests_finished)):
gut.end_run.connect(_on_tests_finished)
gut_config.apply_options(gut)
var run_rest_of_scripts = gut_config.options.unit_test_name == ''
if GutUtils.is_headless():
gut._ignore_pause_before_teardown = true
var run_rest_of_scripts = gut_config.options.unit_test_name == ''
GutErrorTracker.register_logger(error_tracker)
gut.test_scripts(run_rest_of_scripts)
@ -199,6 +211,7 @@ func quit(exit_code):
# Sometimes quitting takes a few seconds. This gives some indicator
# of what is going on.
_gui.set_title("Exiting")
await get_tree().process_frame
lgr.info(str('Exiting with code ', exit_code))

View file

@ -1 +1 @@
uid://bayjc3x1exeie
uid://eg8k46gd42a4

View file

@ -1,6 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://bqy3ikt6vu4b5"]
[ext_resource type="Script" path="res://addons/gut/gui/GutRunner.gd" id="1"]
[ext_resource type="Script" uid="uid://eg8k46gd42a4" path="res://addons/gut/gui/GutRunner.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://m28heqtswbuq" path="res://addons/gut/GutScene.tscn" id="2_6ruxb"]
[node name="GutRunner" type="Node2D"]

View file

@ -2,7 +2,7 @@
[ext_resource type="Theme" uid="uid://cstkhwkpajvqu" path="res://addons/gut/gui/GutSceneTheme.tres" id="1_farmq"]
[ext_resource type="FontFile" uid="uid://bnh0lslf4yh87" path="res://addons/gut/fonts/CourierPrime-Regular.ttf" id="2_a2e2l"]
[ext_resource type="Script" path="res://addons/gut/gui/gut_gui.gd" id="2_eokrf"]
[ext_resource type="Script" uid="uid://blvhsbnsvfyow" path="res://addons/gut/gui/gut_gui.gd" id="2_eokrf"]
[ext_resource type="PackedScene" uid="uid://bvrqqgjpyouse" path="res://addons/gut/gui/ResizeHandle.tscn" id="4_xrhva"]
[node name="Min" type="Panel"]

View file

@ -1,7 +1,7 @@
[gd_scene load_steps=5 format=3 uid="uid://duxblir3vu8x7"]
[ext_resource type="Theme" uid="uid://cstkhwkpajvqu" path="res://addons/gut/gui/GutSceneTheme.tres" id="1_5hlsm"]
[ext_resource type="Script" path="res://addons/gut/gui/gut_gui.gd" id="2_fue6q"]
[ext_resource type="Script" uid="uid://blvhsbnsvfyow" path="res://addons/gut/gui/gut_gui.gd" id="2_fue6q"]
[ext_resource type="FontFile" uid="uid://bnh0lslf4yh87" path="res://addons/gut/fonts/CourierPrime-Regular.ttf" id="2_u5uc1"]
[ext_resource type="PackedScene" uid="uid://bvrqqgjpyouse" path="res://addons/gut/gui/ResizeHandle.tscn" id="4_2r8a8"]
@ -158,17 +158,19 @@ theme_override_font_sizes/font_size = 14
text = "test_this_thing.gd"
text_overrun_behavior = 3
[node name="Spacer1" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
visible = false
[node name="Buttons" type="VBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
layout_mode = 2
size_flags_horizontal = 10
[node name="Continue" type="Button" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
[node name="Continue" type="Button" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/Buttons"]
layout_mode = 2
size_flags_vertical = 4
text = "Continue
"
[node name="WordWrap" type="CheckButton" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/Buttons"]
layout_mode = 2
text = "Word Wrap"
[node name="S3" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
custom_minimum_size = Vector2(5, 0)
layout_mode = 2

View file

@ -89,6 +89,18 @@ var _user_prefs = GutEditorGlobals.user_prefs
var _font_name_pctrl = null
var _font_size_pctrl = null
var keywords = [
['Failed', Color.RED],
['Passed', Color.GREEN],
['Pending', Color.YELLOW],
['Risky', Color.YELLOW],
['Orphans', Color.YELLOW],
['WARNING', Color.YELLOW],
['ERROR', Color.RED],
['ExpectedError', Color.LIGHT_BLUE],
]
# Automatically used when running the OutputText scene from the editor. Changes
# to this method only affect test-running the control through the editor.
func _test_running_setup():
@ -110,6 +122,9 @@ func _test_running_setup():
func _ready():
if(get_parent() is SubViewport):
return
_sr.set_text_edit(_ctrls.output)
_ctrls.use_colors.icon = get_theme_icon('RichTextEffect', 'EditorIcons')
_ctrls.show_search.icon = get_theme_icon('Search', 'EditorIcons')
@ -127,7 +142,7 @@ func _ready():
func _add_other_ctrls():
var fname = 'CourierNew'
var fname = GutUtils.gut_fonts.DEFAULT_CUSTOM_FONT_NAME
if(_user_prefs != null):
fname = _user_prefs.output_font_name.value
_font_name_pctrl = PanelControls.SelectControl.new('Font', fname, GutUtils.avail_fonts,
@ -170,15 +185,6 @@ func _create_highlighter(default_color=Color(1, 1, 1, 1)):
to_return.symbol_color = default_color
to_return.member_variable_color = default_color
var keywords = [
['Failed', Color.RED],
['Passed', Color.GREEN],
['Pending', Color.YELLOW],
['Orphans', Color.YELLOW],
['WARNING', Color.YELLOW],
['ERROR', Color.RED]
]
for keyword in keywords:
to_return.add_keyword_color(keyword[0], keyword[1])
@ -187,13 +193,6 @@ func _create_highlighter(default_color=Color(1, 1, 1, 1)):
func _setup_colors():
_ctrls.output.clear()
var f_color = null
if (_ctrls.output.theme == null) :
f_color = get_theme_color("font_color")
else :
f_color = _ctrls.output.theme.font_color
_highlighter = _create_highlighter()
_ctrls.output.queue_redraw()
@ -300,31 +299,19 @@ func clear():
_ctrls.output.text = ''
func _set_font(font_name, custom_name):
var rtl = _ctrls.output
if(font_name == null):
rtl.remove_theme_font_override(custom_name)
else:
var dyn_font = FontFile.new()
dyn_font.load_dynamic_font('res://addons/gut/fonts/' + font_name + '.ttf')
rtl.add_theme_font_override(custom_name, dyn_font)
func _set_font(custom_name, theme_font_name):
var font = GutUtils.gut_fonts.get_font_for_theme_font_name(theme_font_name, custom_name)
_ctrls.output.add_theme_font_override(theme_font_name, font)
func set_all_fonts(base_name):
_font_name = GutUtils.nvl(base_name, 'Default')
if(base_name == 'Default'):
_set_font(null, 'font')
_set_font(null, 'normal_font')
_set_font(null, 'bold_font')
_set_font(null, 'italics_font')
_set_font(null, 'bold_italics_font')
else:
_set_font(base_name + '-Regular', 'font')
_set_font(base_name + '-Regular', 'normal_font')
_set_font(base_name + '-Bold', 'bold_font')
_set_font(base_name + '-Italic', 'italics_font')
_set_font(base_name + '-BoldItalic', 'bold_italics_font')
_set_font(base_name, 'font')
_set_font(base_name, 'normal_font')
_set_font(base_name, 'bold_font')
_set_font(base_name, 'italics_font')
_set_font(base_name, 'bold_italics_font')
func set_font_size(new_size):

View file

@ -1 +1 @@
uid://ccdonk2lpn1d1
uid://cax5phqs8acmu

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
uid://dp618d4ofjpv1
uid://duf6rfdqr6yoc

View file

@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bvrqqgjpyouse"]
[ext_resource type="Script" path="res://addons/gut/gui/ResizeHandle.gd" id="1_oi5ed"]
[ext_resource type="Script" uid="uid://duf6rfdqr6yoc" path="res://addons/gut/gui/ResizeHandle.gd" id="1_oi5ed"]
[node name="ResizeHandle" type="ColorRect"]
custom_minimum_size = Vector2(20, 20)

View file

@ -1,5 +1,5 @@
@tool
extends Control
extends Tree
var _show_orphans = true
var show_orphans = true :
@ -18,83 +18,55 @@ var _icons = {
green = load('res://addons/gut/images/green.png'),
yellow = load('res://addons/gut/images/yellow.png'),
}
const _col_1_bg_color = Color(0, 0, 0, .1)
@export var script_entry_color : Color = Color(0, 0, 0, .2) :
set(val):
if(val != null):
script_entry_color = val
@export var column_0_color : Color = Color(1, 1, 1, 0) :
set(val):
if(val != null):
column_0_color = val
@export var column_1_color : Color = Color(0, 0, 0, .2):
set(val):
if(val != null):
column_1_color = val
var _max_icon_width = 10
var _root : TreeItem
@onready var _ctrls = {
tree = $Tree,
lbl_overlay = $Tree/TextOverlay
}
@onready var lbl_overlay = $TextOverlay
signal selected(script_path, inner_class, test_name, line_number)
func _debug_ready():
hide_passing = false
load_json_file('user://gut_temp_directory/gut_editor.json')
func _ready():
_root = create_item()
set_hide_root(true)
columns = 2
set_column_expand(0, true)
set_column_expand_ratio(0, 5)
set_column_expand_ratio(1, 1)
set_column_expand(1, true)
item_selected.connect(_on_tree_item_selected)
if(get_parent() == get_tree().root):
_debug_ready()
signal item_selected(script_path, inner_class, test_name, line_number)
# -------------------
# Private
# -------------------
func _ready():
_root = _ctrls.tree.create_item()
_root = _ctrls.tree.create_item()
_ctrls.tree.set_hide_root(true)
_ctrls.tree.columns = 2
_ctrls.tree.set_column_expand(0, true)
_ctrls.tree.set_column_expand(1, false)
_ctrls.tree.set_column_clip_content(0, true)
$Tree.item_selected.connect(_on_tree_item_selected)
if(get_parent() == get_tree().root):
_test_running_setup()
func _test_running_setup():
load_json_file('user://.gut_editor.json')
func _on_tree_item_selected():
var item = _ctrls.tree.get_selected()
var item_meta = item.get_metadata(0)
var item_type = null
# Only select the left side of the tree item, cause I like that better.
# you can still click the right, but only the left gets highlighted.
if(item.is_selected(1)):
item.deselect(1)
item.select(0)
if(item_meta == null):
return
else:
item_type = item_meta.type
var script_path = '';
var line = -1;
var test_name = ''
var inner_class = ''
if(item_type == 'test'):
var s_item = item.get_parent()
script_path = s_item.get_metadata(0)['path']
inner_class = s_item.get_metadata(0)['inner_class']
line = -1
test_name = item.get_text(0)
elif(item_type == 'assert'):
var s_item = item.get_parent().get_parent()
script_path = s_item.get_metadata(0)['path']
inner_class = s_item.get_metadata(0)['inner_class']
line = _get_line_number_from_assert_msg(item.get_text(0))
test_name = item.get_parent().get_text(0)
elif(item_type == 'script'):
script_path = item.get_metadata(0)['path']
if(item.get_parent() != _root):
inner_class = item.get_text(0)
line = -1
test_name = ''
else:
return
item_selected.emit(script_path, inner_class, test_name, line)
func _get_line_number_from_assert_msg(msg):
var line = -1
if(msg.find('at line') > 0):
@ -142,17 +114,7 @@ func _add_script_tree_item(script_path, script_json):
if(parent == null):
parent = _add_script_tree_item(path_info.path, {})
parent.get_metadata(0).inner_tests += script_json['props']['tests']
parent.get_metadata(0).inner_passing += script_json['props']['tests']
parent.get_metadata(0).inner_passing -= script_json['props']['failures']
parent.get_metadata(0).inner_passing -= script_json['props']['pending']
var total_text = str("All ", parent.get_metadata(0).inner_tests, " passed")
if(parent.get_metadata(0).inner_passing != parent.get_metadata(0).inner_tests):
total_text = str(parent.get_metadata(0).inner_passing, '/', parent.get_metadata(0).inner_tests, ' passed.')
parent.set_text(1, total_text)
var item = _ctrls.tree.create_item(parent)
var item = create_item(parent)
item.set_text(0, item_text)
var meta = {
"type":"script",
@ -163,51 +125,46 @@ func _add_script_tree_item(script_path, script_json):
"inner_tests":0
}
item.set_metadata(0, meta)
item.set_custom_bg_color(1, _col_1_bg_color)
item.set_custom_bg_color(0, script_entry_color)
item.set_custom_bg_color(1, script_entry_color)
return item
func _add_assert_item(text, icon, parent_item):
# print(' * adding assert')
var assert_item = _ctrls.tree.create_item(parent_item)
var assert_item = create_item(parent_item)
assert_item.set_icon_max_width(0, _max_icon_width)
assert_item.set_text(0, text)
assert_item.set_metadata(0, {"type":"assert"})
assert_item.set_icon(0, icon)
assert_item.set_custom_bg_color(1, _col_1_bg_color)
assert_item.set_custom_bg_color(0, column_0_color)
assert_item.set_custom_bg_color(1, column_1_color)
return assert_item
func _add_test_tree_item(test_name, test_json, script_item):
# print(' * adding test ', test_name)
var no_orphans_to_show = !_show_orphans or (_show_orphans and test_json.orphans == 0)
var no_orphans_to_show = !_show_orphans or (_show_orphans and test_json.orphan_count == 0)
if(_hide_passing and test_json['status'] == 'pass' and no_orphans_to_show):
return
var item = _ctrls.tree.create_item(script_item)
var item = create_item(script_item)
var status = test_json['status']
var meta = {"type":"test", "json":test_json}
item.set_text(0, test_name)
item.set_text(1, status)
item.set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT)
item.set_custom_bg_color(1, _col_1_bg_color)
item.set_custom_bg_color(1, column_1_color)
item.set_metadata(0, meta)
item.set_icon_max_width(0, _max_icon_width)
var orphan_text = 'orphans'
if(test_json.orphans == 1):
orphan_text = 'orphan'
orphan_text = str(test_json.orphans, ' ', orphan_text)
item.set_custom_bg_color(0, column_0_color)
if(status == 'pass' and no_orphans_to_show):
item.set_icon(0, _icons.green)
elif(status == 'pass' and !no_orphans_to_show):
item.set_icon(0, _icons.yellow)
item.set_text(1, orphan_text)
elif(status == 'fail'):
item.set_icon(0, _icons.red)
else:
@ -223,8 +180,18 @@ func _add_test_tree_item(test_name, test_json, script_item):
for pending in test_json.pending:
_add_assert_item("pending: " + pending.replace("\n", ''), _icons.yellow, item)
if(status != 'pass' and !no_orphans_to_show):
_add_assert_item(orphan_text, _icons.yellow, item)
var orphan_text = 'orphans'
if(test_json.orphan_count == 1):
orphan_text = 'orphan'
orphan_text = str(int(test_json.orphan_count), ' ', orphan_text)
if(!no_orphans_to_show):
var orphan_item = _add_assert_item(orphan_text, _icons.yellow, item)
for o in test_json.orphans:
var orphan_entry = create_item(orphan_item)
orphan_entry.set_text(0, o)
orphan_entry.set_custom_bg_color(0, column_0_color)
orphan_entry.set_custom_bg_color(1, column_1_color)
return item
@ -243,13 +210,17 @@ func _add_script_to_tree(key, script_json):
t_item.collapsed = true
if(s_item.get_children().size() == 0):
s_item.free()
if(script_json.props.skipped):
_add_assert_item("Skipped", _icons.yellow, s_item)
s_item.set_text(1, "Skipped")
else:
s_item.free()
else:
var total_text = str('All ', test_keys.size(), ' passed')
if(bad_count == 0):
s_item.collapsed = true
else:
total_text = str(test_keys.size() - bad_count, '/', test_keys.size(), ' passed')
total_text = str(int(test_keys.size() - bad_count), '/', int(test_keys.size()), ' passed')
s_item.set_text(1, total_text)
@ -276,15 +247,60 @@ func _load_result_tree(j):
var add_count = 0
for key in script_keys:
if(scripts[key]['props']['tests'] > 0):
add_count += 1
_add_script_to_tree(key, scripts[key])
add_count += 1
_add_script_to_tree(key, scripts[key])
_free_childless_scripts()
if(add_count == 0):
add_centered_text('Nothing was run')
else:
_show_all_passed()
# -------------------
# Events
# -------------------
func _on_tree_item_selected():
var item = get_selected()
var item_meta = item.get_metadata(0)
var item_type = null
# Only select the left side of the tree item, cause I like that better.
# you can still click the right, but only the left gets highlighted.
if(item.is_selected(1)):
item.deselect(1)
item.select(0)
if(item_meta == null):
return
else:
item_type = item_meta.type
var script_path = '';
var line = -1;
var test_name = ''
var inner_class = ''
if(item_type == 'test'):
var s_item = item.get_parent()
script_path = s_item.get_metadata(0)['path']
inner_class = s_item.get_metadata(0)['inner_class']
line = -1
test_name = item.get_text(0)
elif(item_type == 'assert'):
var s_item = item.get_parent().get_parent()
script_path = s_item.get_metadata(0)['path']
inner_class = s_item.get_metadata(0)['inner_class']
line = _get_line_number_from_assert_msg(item.get_text(0))
test_name = item.get_parent().get_text(0)
elif(item_type == 'script'):
script_path = item.get_metadata(0)['path']
if(item.get_parent() != _root):
inner_class = item.get_text(0)
line = -1
test_name = ''
else:
return
selected.emit(script_path, inner_class, test_name, line)
# -------------------
@ -313,26 +329,29 @@ func load_json_file(path):
func load_json_results(j):
clear()
if(_root == null):
_root = create_item()
_load_result_tree(j)
func clear():
_ctrls.tree.clear()
_root = _ctrls.tree.create_item()
#func clear():
#clear()
#_root = create_item()
func set_summary_min_width(width):
_ctrls.tree.set_column_custom_minimum_width(1, width)
set_column_custom_minimum_width(1, width)
func add_centered_text(t):
_ctrls.lbl_overlay.visible = true
_ctrls.lbl_overlay.text = t
lbl_overlay.visible = true
lbl_overlay.text = t
func clear_centered_text():
_ctrls.lbl_overlay.visible = false
_ctrls.lbl_overlay.text = ''
lbl_overlay.visible = false
lbl_overlay.text = ''
func collapse_all():
@ -347,7 +366,3 @@ func set_collapsed_on_all(item, value):
item.set_collapsed_recursive(value)
if(item == _root and value):
item.set_collapsed(false)
func get_selected():
return _ctrls.tree.get_selected()

View file

@ -1 +1 @@
uid://dmfojusal8t8m
uid://dehdhn78qv5tr

View file

@ -1,9 +1,28 @@
[gd_scene load_steps=2 format=3 uid="uid://dls5r5f6157nq"]
[ext_resource type="Script" path="res://addons/gut/gui/ResultsTree.gd" id="1_b4uub"]
[ext_resource type="Script" uid="uid://dehdhn78qv5tr" path="res://addons/gut/gui/ResultsTree.gd" id="1_b4uub"]
[node name="ResultsTree" type="VBoxContainer"]
[node name="ResultsTree" type="Tree"]
offset_right = 1082.0
offset_bottom = 544.0
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 2
hide_root = true
script = ExtResource("1_b4uub")
[node name="TextOverlay" type="Label" parent="."]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ResultsTree" type="VBoxContainer" parent="."]
custom_minimum_size = Vector2(10, 10)
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
@ -13,20 +32,3 @@ grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_b4uub")
[node name="Tree" type="Tree" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 2
hide_root = true
[node name="TextOverlay" type="Label" parent="Tree"]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2

View file

@ -1,8 +1,7 @@
@tool
extends Control
var ScriptTextEditors = load('res://addons/gut/gui/script_text_editor_controls.gd')
var EditorCaretContextNotifier = load('res://addons/gut/editor_caret_context_notifier.gd')
@onready var _ctrls = {
btn_script = $HBox/BtnRunScript,
@ -13,16 +12,33 @@ var ScriptTextEditors = load('res://addons/gut/gui/script_text_editor_controls.g
arrow_2 = $HBox/Arrow2
}
var _editors = null
var _cur_editor = null
var _last_line = -1
var _cur_script_path = null
var _caret_notifier = null
var _last_info = {
script = null,
inner_class = null,
test_method = null
method = null
}
var disabled = false :
set(val):
disabled = val
if(is_inside_tree()):
_ctrls.btn_script.disabled = val
_ctrls.btn_inner.disabled = val
_ctrls.btn_method.disabled = val
var method_prefix = 'test_'
var inner_class_prefix = 'Test'
var menu_manager = null :
set(val):
menu_manager = val
menu_manager.run_script.connect(_on_BtnRunScript_pressed)
menu_manager.run_at_cursor.connect(run_at_cursor)
menu_manager.rerun.connect(rerun)
menu_manager.run_inner_class.connect(_on_BtnRunInnerClass_pressed)
menu_manager.run_test.connect(_on_BtnRunMethod_pressed)
_update_buttons(_last_info)
signal run_tests(what)
@ -35,95 +51,103 @@ func _ready():
_ctrls.arrow_1.visible = false
_ctrls.arrow_2.visible = false
# ----------------
# Private
# ----------------
func _set_editor(which):
_last_line = -1
if(_cur_editor != null and _cur_editor.get_ref()):
# _cur_editor.get_ref().disconnect('cursor_changed',Callable(self,'_on_cursor_changed'))
_cur_editor.get_ref().caret_changed.disconnect(_on_cursor_changed)
_caret_notifier = EditorCaretContextNotifier.new()
add_child(_caret_notifier)
_caret_notifier.it_changed.connect(_on_caret_notifer_changed)
if(which != null):
_cur_editor = weakref(which)
which.caret_changed.connect(_on_cursor_changed.bind(which))
# which.connect('cursor_changed',Callable(self,'_on_cursor_changed'),[which])
disabled = disabled
_last_line = which.get_caret_line()
_last_info = _editors.get_line_info()
func _on_caret_notifer_changed(data):
if(data.is_test_script):
_last_info = data
_update_buttons(_last_info)
# ----------------
# Private
# ----------------
func _update_buttons(info):
_ctrls.lbl_none.visible = _cur_script_path == null
_ctrls.btn_script.visible = _cur_script_path != null
_ctrls.lbl_none.visible = false
_ctrls.btn_script.visible = info.script != null
if(info.script != null and info.is_test_script):
_ctrls.btn_script.text = info.script.resource_path.get_file()
_ctrls.btn_inner.visible = info.inner_class != null
_ctrls.arrow_1.visible = info.inner_class != null
_ctrls.btn_inner.text = str(info.inner_class)
_ctrls.btn_inner.tooltip_text = str("Run all tests in Inner-Test-Class ", info.inner_class)
_ctrls.btn_method.visible = info.test_method != null
_ctrls.arrow_2.visible = info.test_method != null
_ctrls.btn_method.text = str(info.test_method)
_ctrls.btn_method.tooltip_text = str("Run test ", info.test_method)
var is_test_method = info.method != null and info.method.begins_with(method_prefix)
_ctrls.btn_method.visible = is_test_method
_ctrls.arrow_2.visible = is_test_method
if(is_test_method):
_ctrls.btn_method.text = str(info.method)
_ctrls.btn_method.tooltip_text = str("Run test ", info.method)
if(menu_manager != null):
menu_manager.disable_menu("run_script", info.script == null)
menu_manager.disable_menu("run_inner_class", info.inner_class == null)
menu_manager.disable_menu("run_at_cursor", info.script == null)
menu_manager.disable_menu("run_test", is_test_method)
menu_manager.disable_menu("rerun", _last_run_info == {})
# The button's new size won't take effect until the next frame.
# This appears to be what was causing the button to not be clickable the
# first time.
call_deferred("_update_size")
_update_size.call_deferred()
func _update_size():
custom_minimum_size.x = _ctrls.btn_method.size.x + _ctrls.btn_method.position.x
var _last_run_info = {}
func _emit_run_tests(info):
_last_run_info = info.duplicate()
run_tests.emit(info)
# ----------------
# Events
# ----------------
func _on_cursor_changed(which):
if(which.get_caret_line() != _last_line):
_last_line = which.get_caret_line()
_last_info = _editors.get_line_info()
_update_buttons(_last_info)
func _on_BtnRunScript_pressed():
var info = _last_info.duplicate()
info.script = _cur_script_path.get_file()
info.script = info.script.resource_path.get_file()
info.inner_class = null
info.test_method = null
emit_signal("run_tests", info)
info.method = null
_emit_run_tests(info)
func _on_BtnRunInnerClass_pressed():
var info = _last_info.duplicate()
info.script = _cur_script_path.get_file()
info.test_method = null
emit_signal("run_tests", info)
info.script = info.script.resource_path.get_file()
info.method = null
_emit_run_tests(info)
func _on_BtnRunMethod_pressed():
var info = _last_info.duplicate()
info.script = _cur_script_path.get_file()
emit_signal("run_tests", info)
info.script = info.script.resource_path.get_file()
_emit_run_tests(info)
# ----------------
# Public
# ----------------
func set_script_text_editors(value):
_editors = value
func rerun():
if(_last_run_info != {}):
_emit_run_tests(_last_run_info)
func activate_for_script(path):
_ctrls.btn_script.visible = true
_ctrls.btn_script.text = path.get_file()
_ctrls.btn_script.tooltip_text = str("Run all tests in script ", path)
_cur_script_path = path
_editors.refresh()
# We have to wait a beat for the visibility to change on
# the editors, otherwise we always get the first one.
await get_tree().process_frame
_set_editor(_editors.get_current_text_edit())
func run_at_cursor():
if(_ctrls.btn_method.visible):
_on_BtnRunMethod_pressed()
elif(_ctrls.btn_inner.visible):
_on_BtnRunInnerClass_pressed()
elif(_ctrls.btn_script.visible):
_on_BtnRunScript_pressed()
else:
print("nothing selected")
func get_script_button():
@ -138,21 +162,10 @@ func get_test_button():
return _ctrls.btn_method
# not used, thought was configurable but it's just the script prefix
func set_method_prefix(value):
_editors.set_method_prefix(value)
# not used, thought was configurable but it's just the script prefix
func set_inner_class_prefix(value):
_editors.set_inner_class_prefix(value)
_caret_notifier.inner_class_prefix = value
# Mashed this function in here b/c it has _editors. Probably should be
# somewhere else (possibly in script_text_editor_controls).
func search_current_editor_for_text(txt):
var te = _editors.get_current_text_edit()
var result = te.search(txt, 0, 0, 0)
var to_return = -1
return to_return
func apply_gut_config(gut_config):
_caret_notifier.script_prefix = gut_config.options.prefix
_caret_notifier.script_suffix = gut_config.options.suffix

View file

@ -1 +1 @@
uid://417tmtcbran2
uid://c4gmgdl1xwflw

View file

@ -1,10 +1,10 @@
[gd_scene load_steps=4 format=3 uid="uid://0yunjxtaa8iw"]
[gd_scene load_steps=3 format=3 uid="uid://0yunjxtaa8iw"]
[ext_resource type="Script" path="res://addons/gut/gui/RunAtCursor.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://cr6tvdv0ve6cv" path="res://addons/gut/gui/play.png" id="2"]
[ext_resource type="Script" uid="uid://c4gmgdl1xwflw" path="res://addons/gut/gui/RunAtCursor.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://6wra5rxmfsrl" path="res://addons/gut/gui/arrow.png" id="3"]
[node name="RunAtCursor" type="Control"]
custom_minimum_size = Vector2(510, 0)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
@ -25,40 +25,35 @@ size_flags_horizontal = 3
size_flags_vertical = 3
[node name="LblNoneSelected" type="Label" parent="HBox"]
visible = false
layout_mode = 2
text = "<None>"
[node name="BtnRunScript" type="Button" parent="HBox"]
visible = false
layout_mode = 2
text = "<script>"
icon = ExtResource("2")
text = "test_test.gd"
[node name="Arrow1" type="TextureButton" parent="HBox"]
visible = false
custom_minimum_size = Vector2(24, 0)
layout_mode = 2
texture_normal = ExtResource("3")
stretch_mode = 3
[node name="BtnRunInnerClass" type="Button" parent="HBox"]
visible = false
layout_mode = 2
text = "<inner class>"
icon = ExtResource("2")
tooltip_text = "Run all tests in Inner-Test-Class TestAssertNe"
text = "TestAssertNe"
[node name="Arrow2" type="TextureButton" parent="HBox"]
visible = false
custom_minimum_size = Vector2(24, 0)
layout_mode = 2
texture_normal = ExtResource("3")
stretch_mode = 3
[node name="BtnRunMethod" type="Button" parent="HBox"]
visible = false
layout_mode = 2
text = "<method>"
icon = ExtResource("2")
tooltip_text = "Run test test_fails_with_integers_equal"
text = "test_fails_with_integers_equal"
[connection signal="pressed" from="HBox/BtnRunScript" to="." method="_on_BtnRunScript_pressed"]
[connection signal="pressed" from="HBox/BtnRunInnerClass" to="." method="_on_BtnRunInnerClass_pressed"]

View file

@ -0,0 +1,216 @@
@tool
extends Control
# I'm probably going to put this back in later and I don't want to create it
# again. Yeah, yeah, yeah.
# class DotsAnimator:
# var text = ''
# var dot = '.'
# var max_dots = 3
# var dot_delay = .5
# var _anim_text = ''
# var _elapsed_time = 0.0
# var _cur_dots = 0
# func get_animated_text():
# return _anim_text
# func add_time(delta):
# _elapsed_time += delta
# if(_elapsed_time > dot_delay):
# _elapsed_time = 0
# _cur_dots += 1
# if(_cur_dots > max_dots):
# _cur_dots = 0
# _anim_text = text.rpad(text.length() + _cur_dots, dot)
var GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')
@onready var btn_kill_it = $BgControl/VBox/Kill
@onready var bg_control = $BgControl
var _pipe_results = {}
var _debug_mode = false
var _std_thread : Thread
var _escape_regex : RegEx = RegEx.new()
var _text_buffer = ''
var bottom_panel = null :
set(val):
bottom_panel = val
bottom_panel.resized.connect(_on_bottom_panel_resized)
var blocking_mode = "Blocking"
var additional_arguments = []
var remove_escape_characters = true
@export var bg_color = Color.WHITE:
set(val):
bg_color = val
if(is_inside_tree()):
bg_control.get("theme_override_styles/panel").bg_color = bg_color
func _debug_ready():
_debug_mode = true
additional_arguments = ['-gselect', 'test_awaiter.gd', '-gconfig', 'res://.gutconfig.json'] # '-gunit_test_name', 'test_can_clear_spies'
blocking_mode = "NonBlocking"
run_tests()
func _ready():
_escape_regex.compile("\\x1b\\[[0-9;]*m")
btn_kill_it.visible = false
if(get_parent() == get_tree().root):
_debug_ready.call_deferred()
bg_color = bg_color
func _process(_delta: float) -> void:
if(_pipe_results != {}):
if(!OS.is_process_running(_pipe_results.pid)):
_end_non_blocking()
# ----------
# Private
# ----------
func _center_me():
position = get_parent().size / 2.0 - size / 2.0
func _output_text(text, should_scroll = true):
if(_debug_mode):
print(text)
else:
if(remove_escape_characters):
text = _escape_regex.sub(text, '', true)
if(bottom_panel != null):
bottom_panel.add_output_text(text)
if(should_scroll):
_scroll_output_pane(-1)
else:
_text_buffer += text
func _scroll_output_pane(line):
if(!_debug_mode and bottom_panel != null):
var txt_ctrl = bottom_panel.get_text_output_control().get_rich_text_edit()
if(line == -1):
line = txt_ctrl.get_line_count()
txt_ctrl.scroll_vertical = line
func _add_arguments_to_output():
if(additional_arguments.size() != 0):
_output_text(
str("Run Mode arguments: ", ' '.join(additional_arguments), "\n\n")
)
func _load_json():
if(_debug_mode):
pass # could load file and print it if we want.
elif(bottom_panel != null):
bottom_panel.load_result_json()
func _run_blocking(options):
btn_kill_it.visible = false
var output = []
await get_tree().create_timer(.1).timeout
OS.execute(OS.get_executable_path(), options, output, true)
_output_text(output[0])
_add_arguments_to_output()
_scroll_output_pane(-1)
_load_json()
queue_free()
func _read_non_blocking_stdio():
while(OS.is_process_running(_pipe_results.pid)):
while(_pipe_results.stderr.get_length() > 0):
_output_text(_pipe_results.stderr.get_line() + "\n")
while(_pipe_results.stdio.get_length() > 0):
_output_text(_pipe_results.stdio.get_line() + "\n")
# without this, things start to lock up.
await get_tree().process_frame
func _run_non_blocking(options):
_pipe_results = OS.execute_with_pipe(OS.get_executable_path(), options, false)
_std_thread = Thread.new()
_std_thread.start(_read_non_blocking_stdio)
btn_kill_it.visible = true
func _end_non_blocking():
_add_arguments_to_output()
_scroll_output_pane(-1)
_load_json()
_pipe_results = {}
_std_thread.wait_to_finish()
_std_thread = null
queue_free()
if(_debug_mode):
get_tree().quit()
# ----------------
# Events
# ----------------
func _on_kill_pressed() -> void:
if(_pipe_results != {} and OS.is_process_running(_pipe_results.pid)):
OS.kill(_pipe_results.pid)
btn_kill_it.visible = false
func _on_color_rect_gui_input(event: InputEvent) -> void:
if(event is InputEventMouseMotion):
if(event.button_mask == MOUSE_BUTTON_MASK_LEFT):
position += event.relative
func _on_bottom_panel_resized():
_center_me()
# ----------------
# Public
# ----------------
func run_tests():
_center_me()
var options = ["-s", "res://addons/gut/gut_cmdln.gd", "-graie", "-gdisable_colors",
"-gconfig", GutEditorGlobals.editor_run_gut_config_path]
options.append_array(additional_arguments)
if(blocking_mode == 'Blocking'):
_run_blocking(options)
else:
_run_non_blocking(options)
func get_godot_help():
_text_buffer = ''
var options = ["--help", "--headless"]
await _run_blocking(options)
return _text_buffer
func get_gut_help():
_text_buffer = ''
var options = ["-s", "res://addons/gut/gut_cmdln.gd", "-gh", "--headless"]
await _run_blocking(options)
return _text_buffer

View file

@ -0,0 +1 @@
uid://bi8pg352un4om

View file

@ -0,0 +1,65 @@
[gd_scene load_steps=3 format=3 uid="uid://cftcb0e6g7tu1"]
[ext_resource type="Script" uid="uid://bi8pg352un4om" path="res://addons/gut/gui/RunExternally.gd" id="1_lrqqi"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_haowt"]
bg_color = Color(0.025935074, 0.17817128, 0.30283752, 1)
corner_radius_top_left = 20
corner_radius_top_right = 20
corner_radius_bottom_right = 20
corner_radius_bottom_left = 20
shadow_size = 5
shadow_offset = Vector2(7, 7)
[node name="DoShellOut" type="Control"]
layout_mode = 3
anchors_preset = 0
offset_right = 774.0
offset_bottom = 260.0
script = ExtResource("1_lrqqi")
bg_color = Color(0.025935074, 0.17817128, 0.30283752, 1)
[node name="BgControl" type="Panel" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_haowt")
[node name="VBox" type="VBoxContainer" parent="BgControl"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Spacer" type="CenterContainer" parent="BgControl/VBox"]
layout_mode = 2
size_flags_vertical = 3
[node name="Title" type="Label" parent="BgControl/VBox"]
layout_mode = 2
text = "Running Tests"
horizontal_alignment = 1
[node name="Spacer2" type="CenterContainer" parent="BgControl/VBox"]
visible = false
layout_mode = 2
size_flags_vertical = 3
[node name="Kill" type="Button" parent="BgControl/VBox"]
visible = false
custom_minimum_size = Vector2(200, 50)
layout_mode = 2
size_flags_horizontal = 4
text = "Stop"
[node name="Spacer3" type="CenterContainer" parent="BgControl/VBox"]
layout_mode = 2
size_flags_vertical = 3
[connection signal="gui_input" from="BgControl" to="." method="_on_color_rect_gui_input"]
[connection signal="pressed" from="BgControl/VBox/Kill" to="." method="_on_kill_pressed"]

View file

@ -4,9 +4,6 @@ extends Control
var GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')
var _interface = null
var _font = null
var _font_size = null
var _editors = null # script_text_editor_controls.gd
var _output_control = null
@onready var _ctrls = {
@ -24,6 +21,9 @@ var _output_control = null
}
func _ready():
if(get_parent() is SubViewport):
return
var f = null
if ($FontSampler.get_label_settings() == null) :
f = get_theme_default_font()
@ -42,7 +42,7 @@ func _ready():
_ctrls.tree.hide_passing = true
_ctrls.toolbar.hide_passing.button_pressed = false
_ctrls.tree.show_orphans = true
_ctrls.tree.item_selected.connect(_on_item_selected)
_ctrls.tree.selected.connect(_on_item_selected)
if(get_parent() == get_tree().root):
_test_running_setup()
@ -127,7 +127,8 @@ func _goto_code(path, line, method_name='', inner_class =''):
search_strings.append(method_name)
await get_tree().process_frame
line = _get_line_number_for_seq_search(search_strings, _editors.get_current_text_edit())
line = _get_line_number_for_seq_search(search_strings,
_interface.get_script_editor().get_current_editor().get_base_editor())
if(line != null and line != -1):
_interface.get_script_editor().goto_line(line)
@ -204,10 +205,6 @@ func set_interface(which):
_interface = which
func set_script_text_editors(value):
_editors = value
func collapse_all():
_ctrls.tree.collapse_all()

View file

@ -1 +1 @@
uid://d1ntrij87eoco
uid://chnko3073tkcv

View file

@ -1,20 +1,9 @@
[gd_scene load_steps=5 format=3 uid="uid://4gyyn12um08h"]
[gd_scene load_steps=4 format=3 uid="uid://4gyyn12um08h"]
[ext_resource type="Script" path="res://addons/gut/gui/RunResults.gd" id="1"]
[ext_resource type="Script" uid="uid://chnko3073tkcv" path="res://addons/gut/gui/RunResults.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://bvo0uao7deu0q" path="res://addons/gut/icon.png" id="2_1k8e0"]
[ext_resource type="PackedScene" uid="uid://dls5r5f6157nq" path="res://addons/gut/gui/ResultsTree.tscn" id="2_o808v"]
[sub_resource type="Image" id="Image_abbh7"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_x655i"]
image = SubResource("Image_abbh7")
[node name="RunResults" type="Control"]
custom_minimum_size = Vector2(345, 0)
layout_mode = 3
@ -33,28 +22,32 @@ layout_mode = 2
size_flags_horizontal = 0
[node name="Expand" type="Button" parent="VBox/Toolbar"]
visible = false
layout_mode = 2
icon = SubResource("ImageTexture_x655i")
icon = ExtResource("2_1k8e0")
[node name="Collapse" type="Button" parent="VBox/Toolbar"]
visible = false
layout_mode = 2
icon = SubResource("ImageTexture_x655i")
icon = ExtResource("2_1k8e0")
[node name="Sep" type="ColorRect" parent="VBox/Toolbar"]
visible = false
custom_minimum_size = Vector2(2, 0)
layout_mode = 2
[node name="LblAll" type="Label" parent="VBox/Toolbar"]
visible = false
layout_mode = 2
text = "All:"
[node name="ExpandAll" type="Button" parent="VBox/Toolbar"]
layout_mode = 2
icon = SubResource("ImageTexture_x655i")
icon = ExtResource("2_1k8e0")
[node name="CollapseAll" type="Button" parent="VBox/Toolbar"]
layout_mode = 2
icon = SubResource("ImageTexture_x655i")
icon = ExtResource("2_1k8e0")
[node name="Sep2" type="ColorRect" parent="VBox/Toolbar"]
custom_minimum_size = Vector2(2, 0)
@ -77,13 +70,13 @@ text = "Sync:"
layout_mode = 2
toggle_mode = true
button_pressed = true
icon = SubResource("ImageTexture_x655i")
icon = ExtResource("2_1k8e0")
[node name="ScrollOutput" type="Button" parent="VBox/Toolbar"]
layout_mode = 2
toggle_mode = true
button_pressed = true
icon = SubResource("ImageTexture_x655i")
icon = ExtResource("2_1k8e0")
[node name="Output" type="Panel" parent="VBox"]
self_modulate = Color(1, 1, 1, 0.541176)

View file

@ -0,0 +1,321 @@
@tool
extends ConfirmationDialog
const RUN_MODE_EDITOR = 'Editor'
const RUN_MODE_BLOCKING = 'Blocking'
const RUN_MODE_NON_BLOCKING = 'NonBlocking'
var GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')
@onready var _bad_arg_dialog = $AcceptDialog
@onready var _main_container = $ScrollContainer/VBoxContainer
var _blurb_style_box = StyleBoxEmpty.new()
var _opt_maker_setup = false
var _arg_vbox : VBoxContainer = null
var _my_ok_button : Button = null
# Run mode button stuff
var _run_mode_theme = load('res://addons/gut/gui/EditorRadioButton.tres')
var _button_group = ButtonGroup.new()
var _btn_in_editor : Button = null
var _btn_blocking : Button = null
var _btn_non_blocking : Button = null
var _txt_additional_arguments = null
var _btn_godot_help = null
var _btn_gut_help = null
var opt_maker = null
var default_path = GutEditorGlobals.run_externally_options_path
# I like this. It holds values loaded/saved which makes for an easy
# reset mechanism. Hit OK; values get written to this object (not the file
# system). Hit Cancel; values are reloaded from this object. Call the
# save/load methods to interact with the file system.
#
# Downside: If the keys/sections in the config file change, this ends up
# preserving old data. So you gotta find a way to clean it out
# somehow.
# Downside solved: Clear the config file at the start of the save method.
var _config_file = ConfigFile.new()
var _run_mode = RUN_MODE_EDITOR
var run_mode = _run_mode:
set(val):
_run_mode = val
if(is_inside_tree()):
_btn_in_editor.button_pressed = _run_mode == RUN_MODE_EDITOR
if(_btn_in_editor.button_pressed):
_btn_in_editor.pressed.emit()
_btn_blocking.button_pressed = _run_mode == RUN_MODE_BLOCKING
if(_btn_blocking.button_pressed):
_btn_blocking.pressed.emit()
_btn_non_blocking.button_pressed = _run_mode == RUN_MODE_NON_BLOCKING
if(_btn_non_blocking.button_pressed):
_btn_non_blocking.pressed.emit()
get():
return _run_mode
var additional_arguments = '' :
get():
if(_opt_maker_setup):
return opt_maker.controls.additional_arguments.value
else:
return additional_arguments
func _debug_ready():
popup_centered()
default_path = GutEditorGlobals.temp_directory.path_join('test_external_run_options.cfg')
exclusive = false
var save_btn = Button.new()
save_btn.text = 'save'
save_btn.pressed.connect(func():
save_to_file()
print(_config_file.encode_to_text()))
save_btn.position = Vector2(100, 20)
save_btn.size = Vector2(100, 100)
get_tree().root.add_child(save_btn)
var load_btn = Button.new()
load_btn.text = 'load'
load_btn.pressed.connect(func():
load_from_file()
print(_config_file.encode_to_text()))
load_btn.position = Vector2(100, 130)
load_btn.size = Vector2(100, 100)
get_tree().root.add_child(load_btn)
var show_btn = Button.new()
show_btn.text = 'Show'
show_btn.pressed.connect(popup_centered)
show_btn.position = Vector2(100, 250)
show_btn.size = Vector2(100, 100)
get_tree().root.add_child(show_btn)
func _ready():
opt_maker = GutUtils.OptionMaker.new(_main_container)
_add_controls()
if(get_parent() == get_tree().root):
_debug_ready.call_deferred()
_my_ok_button = Button.new()
_my_ok_button.text = 'OK'
_my_ok_button.pressed.connect(_validate_and_confirm)
get_ok_button().add_sibling(_my_ok_button)
get_ok_button().modulate.a = 0.0
get_ok_button().text = ''
get_ok_button().disabled = true
canceled.connect(reset)
_button_group.pressed.connect(_on_mode_button_pressed)
run_mode = run_mode
func _validate_and_confirm():
if(validate_arguments()):
_save_to_config_file(_config_file)
confirmed.emit()
hide()
else:
var dlg_text = str("Invalid arguments. The following cannot be used:\n",
' '.join(_invalid_args))
if(run_mode == RUN_MODE_BLOCKING):
dlg_text += str("\nThese cannot be used with blocking mode:\n",
' '.join(_invalid_blocking_args))
_bad_arg_dialog.dialog_text = dlg_text
_bad_arg_dialog.popup_centered()
func _on_mode_button_pressed(which):
if(which == _btn_in_editor):
_arg_vbox.modulate.a = .3
else:
_arg_vbox.modulate.a = 1.0
_txt_additional_arguments.value_ctrl.editable = which != _btn_in_editor
if(which == _btn_in_editor):
_run_mode = RUN_MODE_EDITOR
elif(which == _btn_blocking):
_run_mode = RUN_MODE_BLOCKING
elif(which == _btn_non_blocking):
_run_mode = RUN_MODE_NON_BLOCKING
func _add_run_mode_button(text, desc_label, description):
var btn = Button.new()
btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
btn.toggle_mode = true
btn.text = text
btn.button_group = _button_group
btn.theme = _run_mode_theme
btn.pressed.connect(func(): desc_label.text = str('[b]', text, "[/b]\n", description))
return btn
func _add_blurb(text):
var ctrl = opt_maker.add_blurb(text)
ctrl.set("theme_override_styles/normal", _blurb_style_box)
return ctrl
func _add_title(text):
var ctrl = opt_maker.add_title(text)
ctrl.get_child(0).horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
return ctrl
func _add_controls():
_add_title("Run Modes")
_add_blurb(
"Choose how GUT will launch tests. Normally you just run them through the editor, but now " +
"you can run them externally. This is an experimental feature. It has been tested on Mac " +
"and Windows. Your results may vary. Feedback welcome at [url]https://github.com/bitwes/Gut/issues[/url].\n ")
var button_desc_box = HBoxContainer.new()
var button_box = VBoxContainer.new()
var button_desc = RichTextLabel.new()
button_desc.fit_content = true
button_desc.bbcode_enabled = true
button_desc.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button_desc.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_main_container.add_child(button_desc_box)
button_desc_box.add_child(button_box)
button_desc_box.add_child(button_desc)
_btn_in_editor = _add_run_mode_button("In Editor (default)", button_desc,
"This is the default. Runs through the editor. When an error occurs " +
"the debugger is invoked. [b]print[/b] output " +
"appears in the Output panel and errors show up in the Debugger panel.")
button_box.add_child(_btn_in_editor)
_btn_blocking = _add_run_mode_button("Externally - Blocking", button_desc,
"Debugger is not enabled, and cannot be enabled. All output (print, errors, warnings, etc) " +
"appears in the GUT panel, and [b]not[/b] the Output or Debugger panels. \n" +
"The Editor cannot be used while tests are running. If you are trying to test for errors, this " +
"mode provides the best output.")
button_box.add_child(_btn_blocking)
_btn_non_blocking = _add_run_mode_button("Externally - NonBlocking", button_desc,
"Debugger is not enabled, and cannot be enabled. All output (print, errors, warnings, etc) " +
"appears in the GUT panel, and [b]not[/b] the Output or Debugger panels. \n" +
"Test output is streamed to the GUT panel. The editor is not blocked, but can be less " +
"responsive when there is a lot of output. This is the only mode that supports the --headless argument." )
button_box.add_child(_btn_non_blocking)
_add_title("Command Line Arguments")
_arg_vbox = VBoxContainer.new()
_main_container.add_child(_arg_vbox)
opt_maker.base_container = _arg_vbox
_txt_additional_arguments = opt_maker.add_value("additional_arguments", additional_arguments, '', '')
_txt_additional_arguments.value_ctrl.placeholder_text = "Put your arguments here. Ex: --verbose -glog 0"
_txt_additional_arguments.value_ctrl.select_all_on_focus = false
_add_blurb(
"Supply any command line options for GUT and/or Godot when running externally. You cannot use " +
"spaces in values. See the Godot and GUT documentation for valid arguments. GUT arguments " +
"specified here take precedence over your config.")
_add_blurb("[b]Be Careful[/b] There are plenty of argument combinations that may make this " +
"act wrong/odd/bad/horrible. Some arguments you might [i]want[/i] " +
"to use but [b]shouldn't[/b] are checked for, but not that many. Choose your arguments carefully (generally good advice).")
opt_maker.base_container = _main_container
_add_title("Display CLI Help")
_add_blurb("You can use these buttons to get a list of valid GUT and Godot options. They print the CLI help text for each to the [b]Output Panel[/b].")
_btn_godot_help = Button.new()
_btn_godot_help.text = "Print Godot CLI Help"
_main_container.add_child(_btn_godot_help)
_btn_godot_help.pressed.connect(func():
await _show_help("get_godot_help"))
_btn_gut_help = Button.new()
_btn_gut_help.text = "Print GUT CLI Help"
_main_container.add_child(_btn_gut_help)
_btn_gut_help.pressed.connect(func():
await _show_help("get_gut_help"))
_opt_maker_setup = true
func _show_help(help_method_name):
_btn_godot_help.disabled = true
_btn_gut_help.disabled = true
var re = GutUtils.RunExternallyScene.instantiate()
add_child(re)
re.visible = false
var text = await re.call(help_method_name)
print(text)
re.queue_free()
_btn_godot_help.disabled = false
_btn_gut_help.disabled = false
if(GutEditorGlobals.gut_plugin != null):
GutEditorGlobals.gut_plugin.show_output_panel()
func _save_to_config_file(f : ConfigFile):
f.clear()
f.set_value('main', 'run_mode', run_mode)
f.set_value('main', 'additional_arguments', opt_maker.controls.additional_arguments.value)
func save_to_file(path = default_path):
_save_to_config_file(_config_file)
_config_file.save(path)
func _load_from_config_file(f):
run_mode = f.get_value('main', 'run_mode', RUN_MODE_EDITOR)
opt_maker.controls.additional_arguments.value = \
f.get_value('main', 'additional_arguments', '')
func load_from_file(path = default_path):
_config_file.load(path)
_load_from_config_file(_config_file)
func reset():
_load_from_config_file(_config_file)
func get_additional_arguments_array():
return additional_arguments.split(" ", false)
func should_run_externally():
return run_mode != RUN_MODE_EDITOR
var _invalid_args = [
'-d', '--debug',
'-s', '--script',
'-e', '--editor'
]
var _invalid_blocking_args = [
'--headless'
]
func validate_arguments():
var arg_array = get_additional_arguments_array()
var i = 0
var invalid_found = false
while i < _invalid_args.size() and !invalid_found:
if(arg_array.has(_invalid_args[i])):
invalid_found = true
i += 1
if(run_mode == RUN_MODE_BLOCKING):
i = 0
while i < _invalid_blocking_args.size() and !invalid_found:
if(arg_array.has(_invalid_blocking_args[i])):
invalid_found = true
i += 1
return !invalid_found
func get_godot_help():
return ''

View file

@ -0,0 +1 @@
uid://c64u22kybimgi

View file

@ -0,0 +1,34 @@
[gd_scene load_steps=2 format=3 uid="uid://ckv5eh8xyrwbk"]
[ext_resource type="Script" uid="uid://c64u22kybimgi" path="res://addons/gut/gui/ShellOutOptions.gd" id="1_ht2pf"]
[node name="ShellOutOptions" type="ConfirmationDialog"]
oversampling_override = 1.0
title = "GUT Run Mode (Experimental)"
position = Vector2i(0, 36)
size = Vector2i(516, 557)
visible = true
script = ExtResource("1_ht2pf")
[node name="ScrollContainer" type="ScrollContainer" parent="."]
custom_minimum_size = Vector2(500, 500)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="AcceptDialog" type="AcceptDialog" parent="."]
oversampling_override = 1.0
size = Vector2i(399, 106)
dialog_text = "Invalid arguments. The following cannot be used:
-d --debug -s --script"

View file

@ -120,6 +120,8 @@ func get_shortcut():
to_return.events.append(_source_event)
return to_return
func get_input_event():
return _source_event
func set_shortcut(sc):
if(sc == null or sc.events == null || sc.events.size() <= 0):

View file

@ -1 +1 @@
uid://byhinwdh4mxic
uid://k6hvvpekp0xw

View file

@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://sfb1fw8j6ufu"]
[ext_resource type="Script" path="res://addons/gut/gui/ShortcutButton.gd" id="1"]
[ext_resource type="Script" uid="uid://k6hvvpekp0xw" path="res://addons/gut/gui/ShortcutButton.gd" id="1"]
[node name="ShortcutButton" type="Control"]
custom_minimum_size = Vector2(210, 30)

View file

@ -0,0 +1,120 @@
@tool
extends ConfirmationDialog
var GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')
var default_path = GutEditorGlobals.editor_shortcuts_path
@onready var scbtn_run_all = $Scroll/Layout/CRunAll/ShortcutButton
@onready var scbtn_run_current_script = $Scroll/Layout/CRunCurrentScript/ShortcutButton
@onready var scbtn_run_current_inner = $Scroll/Layout/CRunCurrentInner/ShortcutButton
@onready var scbtn_run_current_test = $Scroll/Layout/CRunCurrentTest/ShortcutButton
@onready var scbtn_run_at_cursor = $Scroll/Layout/CRunAtCursor/ShortcutButton
@onready var scbtn_rerun = $Scroll/Layout/CRerun/ShortcutButton
@onready var scbtn_panel = $Scroll/Layout/CPanelButton/ShortcutButton
@onready var scbtn_windowed = $Scroll/Layout/CToggleWindowed/ShortcutButton
@onready var all_buttons = [
scbtn_run_all, scbtn_run_current_script, scbtn_run_current_inner,
scbtn_run_current_test, scbtn_run_at_cursor, scbtn_rerun,
scbtn_panel, scbtn_windowed
]
func _debug_ready():
popup_centered()
var btn = Button.new()
btn.text = "show"
get_tree().root.add_child(btn)
btn.pressed.connect(popup)
btn.position = Vector2(100, 100)
btn.size = Vector2(100, 100)
size_changed.connect(func(): title = str(size))
func _ready():
for scbtn in all_buttons:
scbtn.connect('start_edit', _on_edit_start.bind(scbtn))
scbtn.connect('end_edit', _on_edit_end)
canceled.connect(_on_cancel)
# Sizing this window on different monitors, especially compared to what it
# looks like if you just run this project is annoying. This is what I came
# up with after getting annoyed. You probably won't be looking at this
# very often so it's fine...until it isn't.
size = Vector2(DisplayServer.screen_get_size()) * Vector2(.5, .8)
if(get_parent() == get_tree().root):
_debug_ready.call_deferred()
func _cancel_all():
for scbtn in all_buttons:
scbtn.cancel()
# ------------
# Events
# ------------
func _on_cancel():
_cancel_all()
load_shortcuts()
func _on_edit_start(which):
for scbtn in all_buttons:
if(scbtn != which):
scbtn.disable_set(true)
scbtn.disable_clear(true)
func _on_edit_end():
for scbtn in all_buttons:
scbtn.disable_set(false)
scbtn.disable_clear(false)
# ------------
# Public
# ------------
func save_shortcuts():
save_shortcuts_to_file(default_path)
func save_shortcuts_to_file(path):
var f = ConfigFile.new()
f.set_value('main', 'panel_button', scbtn_panel.get_shortcut())
f.set_value('main', 'rerun', scbtn_rerun.get_shortcut())
f.set_value('main', 'run_all', scbtn_run_all.get_shortcut())
f.set_value('main', 'run_at_cursor', scbtn_run_at_cursor.get_shortcut())
f.set_value('main', 'run_current_inner', scbtn_run_current_inner.get_shortcut())
f.set_value('main', 'run_current_script', scbtn_run_current_script.get_shortcut())
f.set_value('main', 'run_current_test', scbtn_run_current_test.get_shortcut())
# f.set_value('main', 'toggle_windowed', scbtn_windowed.get_shortcut())
f.save(path)
func load_shortcuts():
load_shortcuts_from_file(default_path)
func load_shortcuts_from_file(path):
var f = ConfigFile.new()
# as long as this shortcut is never modified, this is fine, otherwise
# each thing should get its own default instead.
var empty = Shortcut.new()
f.load(path)
scbtn_panel.set_shortcut(f.get_value('main', 'panel_button', empty))
scbtn_rerun.set_shortcut(f.get_value('main', 'rerun', empty))
scbtn_run_all.set_shortcut(f.get_value('main', 'run_all', empty))
scbtn_run_at_cursor.set_shortcut(f.get_value('main', 'run_at_cursor', empty))
scbtn_run_current_inner.set_shortcut(f.get_value('main', 'run_current_inner', empty))
scbtn_run_current_script.set_shortcut(f.get_value('main', 'run_current_script', empty))
scbtn_run_current_test.set_shortcut(f.get_value('main', 'run_current_test', empty))
# scbtn_windowed.set_shortcut(f.get_value('main', 'toggle_windowed', empty))

View file

@ -0,0 +1 @@
uid://dc5jgemxslgvl

View file

@ -0,0 +1,209 @@
[gd_scene format=3 uid="uid://dj5ve0bq7xa5j"]
[ext_resource type="Script" uid="uid://dc5jgemxslgvl" path="res://addons/gut/gui/ShortcutDialog.gd" id="1_qq8qn"]
[ext_resource type="PackedScene" uid="uid://sfb1fw8j6ufu" path="res://addons/gut/gui/ShortcutButton.tscn" id="2_i3wie"]
[node name="ShortcutDialog" type="ConfirmationDialog" unique_id=1776753623]
oversampling_override = 1.0
title = "GUT Shortcuts"
position = Vector2i(0, 36)
size = Vector2i(1920, 1728)
visible = true
script = ExtResource("1_qq8qn")
[node name="Scroll" type="ScrollContainer" parent="." unique_id=1914806303]
offset_left = 8.0
offset_top = 8.0
offset_right = 1912.0
offset_bottom = 1679.0
[node name="Layout" type="VBoxContainer" parent="Scroll" unique_id=501181924]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="ShortcutDescription" type="RichTextLabel" parent="Scroll/Layout" unique_id=945401700]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
bbcode_enabled = true
text = "Shortcuts for:
- Buttons in Panel
- Project->Tools->GUT menu items
Shortcuts that only apply to menus are labeled."
fit_content = true
scroll_active = false
[node name="TopPad" type="CenterContainer" parent="Scroll/Layout" unique_id=1405248155]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
[node name="CPanelButton" type="HBoxContainer" parent="Scroll/Layout" unique_id=1970478925]
layout_mode = 2
[node name="Label" type="Label" parent="Scroll/Layout/CPanelButton" unique_id=1534644867]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Show/Hide GUT"
[node name="ShortcutButton" parent="Scroll/Layout/CPanelButton" unique_id=2131329924 instance=ExtResource("2_i3wie")]
layout_mode = 2
size_flags_horizontal = 3
[node name="ShortcutDescription2" type="RichTextLabel" parent="Scroll/Layout" unique_id=2145896902]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
bbcode_enabled = true
text = "[i]Show/hide the gut panel or move focus to/away from the GUT window.
[/i]"
fit_content = true
scroll_active = false
[node name="CRunAll" type="HBoxContainer" parent="Scroll/Layout" unique_id=1158078093]
layout_mode = 2
[node name="Label" type="Label" parent="Scroll/Layout/CRunAll" unique_id=1066237403]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run All"
[node name="ShortcutButton" parent="Scroll/Layout/CRunAll" unique_id=738525032 instance=ExtResource("2_i3wie")]
layout_mode = 2
size_flags_horizontal = 3
[node name="ShortcutDescription3" type="RichTextLabel" parent="Scroll/Layout" unique_id=140757094]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
bbcode_enabled = true
text = "[i]Run the entire test suite.[/i]"
fit_content = true
scroll_active = false
[node name="CRunCurrentScript" type="HBoxContainer" parent="Scroll/Layout" unique_id=736016343]
layout_mode = 2
[node name="Label" type="Label" parent="Scroll/Layout/CRunCurrentScript" unique_id=950095716]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run Current Script"
[node name="ShortcutButton" parent="Scroll/Layout/CRunCurrentScript" unique_id=983079158 instance=ExtResource("2_i3wie")]
layout_mode = 2
size_flags_horizontal = 3
[node name="ShortcutDescription4" type="RichTextLabel" parent="Scroll/Layout" unique_id=1806489359]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
bbcode_enabled = true
text = "[i]Run all tests in the currently selected script.[/i]"
fit_content = true
scroll_active = false
[node name="CRunCurrentInner" type="HBoxContainer" parent="Scroll/Layout" unique_id=2095658353]
layout_mode = 2
[node name="Label" type="Label" parent="Scroll/Layout/CRunCurrentInner" unique_id=1492835813]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run Current Inner Class"
[node name="ShortcutButton" parent="Scroll/Layout/CRunCurrentInner" unique_id=1400542413 instance=ExtResource("2_i3wie")]
layout_mode = 2
size_flags_horizontal = 3
[node name="ShortcutDescription5" type="RichTextLabel" parent="Scroll/Layout" unique_id=1669546632]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
bbcode_enabled = true
text = "[i]Run only the currently selected inner test class if one is selected.[/i]"
fit_content = true
scroll_active = false
[node name="CRunCurrentTest" type="HBoxContainer" parent="Scroll/Layout" unique_id=641427686]
layout_mode = 2
[node name="Label" type="Label" parent="Scroll/Layout/CRunCurrentTest" unique_id=879088006]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run Current Test"
[node name="ShortcutButton" parent="Scroll/Layout/CRunCurrentTest" unique_id=1073304800 instance=ExtResource("2_i3wie")]
layout_mode = 2
size_flags_horizontal = 3
[node name="ShortcutDescription6" type="RichTextLabel" parent="Scroll/Layout" unique_id=164300543]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
bbcode_enabled = true
text = "[i]Run only the currently selected test, if one is selected[/i]"
fit_content = true
scroll_active = false
[node name="CRunAtCursor" type="HBoxContainer" parent="Scroll/Layout" unique_id=1201163993]
layout_mode = 2
[node name="Label" type="Label" parent="Scroll/Layout/CRunAtCursor" unique_id=1748949401]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run At Cursor (menu only)"
[node name="ShortcutButton" parent="Scroll/Layout/CRunAtCursor" unique_id=282512521 instance=ExtResource("2_i3wie")]
layout_mode = 2
size_flags_horizontal = 3
[node name="ShortcutDescription7" type="RichTextLabel" parent="Scroll/Layout" unique_id=1040671512]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
bbcode_enabled = true
text = "[i]Run the most specific test/inner class/script based on where the cursor is.[/i]"
fit_content = true
scroll_active = false
[node name="CRerun" type="HBoxContainer" parent="Scroll/Layout" unique_id=1620841467]
layout_mode = 2
[node name="Label" type="Label" parent="Scroll/Layout/CRerun" unique_id=1644131487]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Rerun (menu only)"
[node name="ShortcutButton" parent="Scroll/Layout/CRerun" unique_id=782836241 instance=ExtResource("2_i3wie")]
layout_mode = 2
size_flags_horizontal = 3
[node name="ShortcutDescription8" type="RichTextLabel" parent="Scroll/Layout" unique_id=520952349]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
bbcode_enabled = true
text = "[i]Rerun the test(s) that were last run."
fit_content = true
scroll_active = false
[node name="CToggleWindowed" type="HBoxContainer" parent="Scroll/Layout" unique_id=1521839247]
visible = false
layout_mode = 2
[node name="Label" type="Label" parent="Scroll/Layout/CToggleWindowed" unique_id=728639840]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Toggle Windowed"
[node name="ShortcutButton" parent="Scroll/Layout/CToggleWindowed" unique_id=603114021 instance=ExtResource("2_i3wie")]
layout_mode = 2
size_flags_horizontal = 3
[node name="ShortcutDescription9" type="RichTextLabel" parent="Scroll/Layout" unique_id=747035069]
visible = false
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
bbcode_enabled = true
text = "[i]Toggle GUT in the bottom panel or a separate window.[/i]"
fit_content = true
scroll_active = false

125
addons/gut/gui/about.gd Normal file
View file

@ -0,0 +1,125 @@
@tool
extends AcceptDialog
var GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')
var _bbcode = \
"""
[center]GUT {gut_version}[/center]
[center][b]GUT Links[/b]
{gut_link_table}[/center]
[center][b]VSCode Extension Links[/b]
{vscode_link_table}[/center]
[center]You can support GUT development at
{donate_link}
Thanks for using GUT!
[/center]
"""
var _gut_links = [
[&"Documentation", &"https://gut.readthedocs.io"],
[&"What's New", &"https://github.com/bitwes/Gut/releases/tag/v{gut_version}"],
[&"Repo", &"https://github.com/bitwes/gut"],
[&"Report Bugs", &"https://github.com/bitwes/gut/issues"]
]
var _vscode_links = [
["Repo", "https://github.com/bitwes/gut-extension"],
["Market Place", "https://marketplace.visualstudio.com/items?itemName=bitwes.gut-extension"]
]
var _donate_link = "https://buymeacoffee.com/bitwes"
@onready var _logo = $Logo
func _ready():
if(get_parent() is SubViewport):
return
_vert_center_logo()
$Logo.disabled = true
$HBox/Scroll/RichTextLabel.text = _make_text()
func _color_link(link_text):
return str("[color=ROYAL_BLUE]", link_text, "[/color]")
func _link_table(entries):
var text = ''
for entry in entries:
text += str("[cell][right]", entry[0], "[/right][/cell]")
var link = str("[url]", entry[1], "[/url]")
if(entry[1].length() > 60):
link = str("[url=", entry[1], "]", entry[1].substr(0, 50), "...[/url]")
text += str("[cell][left]", _color_link(link), "[/left][/cell]\n")
return str('[table=2]', text, '[/table]')
func _make_text():
var gut_link_table = _link_table(_gut_links)
var vscode_link_table = _link_table(_vscode_links)
var text = _bbcode.format({
"gut_link_table":gut_link_table,
"vscode_link_table":vscode_link_table,
"donate_link":_color_link(str('[url]', _donate_link, '[/url]')),
"gut_version":GutUtils.version_numbers.gut_version,
})
return text
func _vert_center_logo():
_logo.position.y = size.y / 2.0
# -----------
# Events
# -----------
func _on_rich_text_label_meta_clicked(meta: Variant) -> void:
OS.shell_open(str(meta))
func _on_mouse_entered() -> void:
pass#_logo.active = true
func _on_mouse_exited() -> void:
pass#_logo.active = false
var _odd_ball_eyes_l = 1.1
var _odd_ball_eyes_r = .7
func _on_rich_text_label_meta_hover_started(meta: Variant) -> void:
if(meta == _gut_links[0][1]):
_logo.set_eye_color(Color.RED)
elif(meta.find("releases/tag/") > 0):
_logo.set_eye_color(Color.GREEN)
elif(meta == _gut_links[2][1]):
_logo.set_eye_color(Color.PURPLE)
elif(meta == _gut_links[3][1]):
_logo.set_eye_scale(1.2)
elif(meta == _vscode_links[0][1]):
_logo.set_eye_scale(.5, .5)
elif(meta == _vscode_links[1][1]):
_logo.set_eye_scale(_odd_ball_eyes_l, _odd_ball_eyes_r)
var temp = _odd_ball_eyes_l
_odd_ball_eyes_l = _odd_ball_eyes_r
_odd_ball_eyes_r = temp
elif(meta == _donate_link):
_logo.active = false
func _on_rich_text_label_meta_hover_ended(meta: Variant) -> void:
if(meta == _donate_link):
_logo.active = true
func _on_logo_pressed() -> void:
_logo.disabled = !_logo.disabled

View file

@ -0,0 +1 @@
uid://g7qu8ihdt3pd

56
addons/gut/gui/about.tscn Normal file
View file

@ -0,0 +1,56 @@
[gd_scene load_steps=5 format=3 uid="uid://dqbkylpsatcqm"]
[ext_resource type="Script" uid="uid://g7qu8ihdt3pd" path="res://addons/gut/gui/about.gd" id="1_bg86c"]
[ext_resource type="PackedScene" uid="uid://bjkn8mhx2fmt1" path="res://addons/gut/gui/GutLogo.tscn" id="3_kpic4"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q8rky"]
bg_color = Color(0, 0, 0, 0.49803922)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kpic4"]
[node name="About" type="AcceptDialog"]
oversampling_override = 1.0
title = "About GUT"
position = Vector2i(0, 36)
size = Vector2i(1500, 800)
visible = true
min_size = Vector2i(800, 800)
script = ExtResource("1_bg86c")
[node name="HBox" type="HBoxContainer" parent="."]
offset_left = 8.0
offset_top = 8.0
offset_right = 1492.0
offset_bottom = 751.0
alignment = 1
[node name="MakeRoomForLogo" type="CenterContainer" parent="HBox"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
[node name="Scroll" type="ScrollContainer" parent="HBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="RichTextLabel" type="RichTextLabel" parent="HBox/Scroll"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_styles/normal = SubResource("StyleBoxFlat_q8rky")
theme_override_styles/focus = SubResource("StyleBoxEmpty_kpic4")
bbcode_enabled = true
fit_content = true
[node name="Logo" parent="." instance=ExtResource("3_kpic4")]
modulate = Color(0.74509805, 0.74509805, 0.74509805, 1)
position = Vector2(151, 265)
scale = Vector2(0.8, 0.8)
active = true
disabled = true
[connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"]
[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"]
[connection signal="meta_clicked" from="HBox/Scroll/RichTextLabel" to="." method="_on_rich_text_label_meta_clicked"]
[connection signal="meta_hover_ended" from="HBox/Scroll/RichTextLabel" to="." method="_on_rich_text_label_meta_hover_ended"]
[connection signal="meta_hover_started" from="HBox/Scroll/RichTextLabel" to="." method="_on_rich_text_label_meta_hover_started"]
[connection signal="pressed" from="Logo" to="." method="_on_logo_pressed"]

View file

@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/arrow.png-2b5b2d838b5b3467cf300ac2da1630d9.ct
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
@ -25,6 +27,10 @@ mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false

View file

@ -11,9 +11,10 @@ static var editor_run_gut_config_path = 'gut_editor_config.json':
get: return temp_directory.path_join(editor_run_gut_config_path)
# Should this print a message or something instead? Probably, but then I'd
# be repeating even more code than if this was just a constant. So I didn't,
# even though I wanted to put make the message a easter eggish fun message.
# even though I wanted to make the message a easter eggish fun message.
# I didn't, so this dumb comment will have to serve as the easter eggish fun.
set(v): pass
set(v):
print("Be sure to document your code. Never trust comments.")
static var editor_run_bbcode_results_path = 'gut_editor.bbcode':
@ -30,6 +31,9 @@ static var editor_shortcuts_path = 'gut_editor_shortcuts.cfg' :
get: return temp_directory.path_join(editor_shortcuts_path)
set(v): pass
static var run_externally_options_path = 'gut_editor_run_externally.cfg' :
get: return temp_directory.path_join(run_externally_options_path)
set(v): pass
static var _user_prefs = null
static var user_prefs = _user_prefs :
@ -38,10 +42,27 @@ static var user_prefs = _user_prefs :
# editor.
get:
if(_user_prefs == null and Engine.is_editor_hint()):
_user_prefs = GutUserPreferences.new(EditorInterface.get_editor_settings())
# This is sometimes used when not in the editor. Avoid parser error
# for EditorInterface.
_user_prefs = GutUserPreferences.new(GutUtils.get_editor_interface().get_editor_settings())
return _user_prefs
static var gut_plugin = null
static func create_temp_directory():
DirAccess.make_dir_recursive_absolute(temp_directory)
static func is_being_edited_in_editor(which):
if(!Engine.is_editor_hint()):
return false
var trav = which
var is_scene_root = false
var editor_root = which.get_tree().edited_scene_root
while(trav != null and !is_scene_root):
is_scene_root = editor_root == trav
if(!is_scene_root):
trav = trav.get_parent()
return is_scene_root

View file

@ -1 +1 @@
uid://b0cay8uv6i2kn
uid://cbi00ubn046c2

View file

@ -3,105 +3,33 @@ var GutConfig = load('res://addons/gut/gut_config.gd')
const DIRS_TO_LIST = 6
var _base_container = null
# All the various PanelControls indexed by thier gut_config keys.
var _cfg_ctrls = {}
# specific titles that we need to do stuff with
var _titles = {
dirs = null
}
# All titles so we can free them when we want.
var _all_titles = []
var _cfg_ctrls = {}
var opt_maker = null
func _init(cont):
_base_container = cont
func _add_title(text):
var row = PanelControls.BaseGutPanelControl.new(text, text)
_base_container.add_child(row)
row.connect('draw', _on_title_cell_draw.bind(row))
_all_titles.append(row)
return row
func _add_ctrl(key, ctrl):
_cfg_ctrls[key] = ctrl
_base_container.add_child(ctrl)
func _add_number(key, value, disp_text, v_min, v_max, hint=''):
var ctrl = PanelControls.NumberControl.new(disp_text, value, v_min, v_max, hint)
_add_ctrl(key, ctrl)
return ctrl
func _add_select(key, value, values, disp_text, hint=''):
var ctrl = PanelControls.SelectControl.new(disp_text, value, values, hint)
_add_ctrl(key, ctrl)
return ctrl
func _add_value(key, value, disp_text, hint=''):
var ctrl = PanelControls.StringControl.new(disp_text, value, hint)
_add_ctrl(key, ctrl)
return ctrl
func _add_boolean(key, value, disp_text, hint=''):
var ctrl = PanelControls.BooleanControl.new(disp_text, value, hint)
_add_ctrl(key, ctrl)
return ctrl
func _add_directory(key, value, disp_text, hint=''):
var ctrl = PanelControls.DirectoryControl.new(disp_text, value, hint)
_add_ctrl(key, ctrl)
ctrl.dialog.title = disp_text
return ctrl
func _add_file(key, value, disp_text, hint=''):
var ctrl = PanelControls.DirectoryControl.new(disp_text, value, hint)
_add_ctrl(key, ctrl)
ctrl.dialog.file_mode = ctrl.dialog.FILE_MODE_OPEN_FILE
ctrl.dialog.title = disp_text
return ctrl
func _add_save_file_anywhere(key, value, disp_text, hint=''):
var ctrl = PanelControls.DirectoryControl.new(disp_text, value, hint)
_add_ctrl(key, ctrl)
ctrl.dialog.file_mode = ctrl.dialog.FILE_MODE_SAVE_FILE
ctrl.dialog.access = ctrl.dialog.ACCESS_FILESYSTEM
ctrl.dialog.title = disp_text
return ctrl
func _add_color(key, value, disp_text, hint=''):
var ctrl = PanelControls.ColorControl.new(disp_text, value, hint)
_add_ctrl(key, ctrl)
return ctrl
opt_maker = GutUtils.OptionMaker.new(cont)
_cfg_ctrls = opt_maker.controls
# _base_container = cont
func _add_save_load():
var ctrl = PanelControls.SaveLoadControl.new('Config', '', '')
_base_container.add_child(ctrl)
ctrl.save_path_chosen.connect(_on_save_path_chosen)
ctrl.load_path_chosen.connect(_on_load_path_chosen)
_cfg_ctrls['save_load'] = ctrl
#_cfg_ctrls['save_load'] = ctrl
opt_maker.add_ctrl('save_load', ctrl)
return ctrl
# ------------------
# Events
# ------------------
func _on_title_cell_draw(which):
which.draw_rect(Rect2(Vector2(0, 0), which.size), Color(0, 0, 0, .15))
func _on_save_path_chosen(path):
save_file(path)
@ -145,15 +73,7 @@ func get_config_issues():
func clear():
for key in _cfg_ctrls:
_cfg_ctrls[key].free()
_cfg_ctrls.clear()
for entry in _all_titles:
entry.free()
_all_titles.clear()
opt_maker.clear()
func save_file(path):
@ -191,54 +111,65 @@ func set_options(opts):
# _add_title('Save/Load')
_add_save_load()
_add_title("Settings")
_add_number("log_level", options.log_level, "Log Level", 0, 3,
opt_maker.add_title("Settings")
opt_maker.add_number("log_level", options.log_level, "Log Level", 0, 3,
"Detail level for log messages.\n" + \
"\t0: Errors and failures only.\n" + \
"\t1: Adds all test names + warnings + info\n" + \
"\t2: Shows all asserts\n" + \
"\t3: Adds more stuff probably, maybe not.")
_add_boolean('ignore_pause', options.ignore_pause, 'Ignore Pause',
opt_maker.add_float("wait_log_delay", options.wait_log_delay, "Wait Log Delay", 0.1, 0.0, 999.1,
"How long to wait before displaying 'Awaiting' messages.")
opt_maker.add_boolean('ignore_pause', options.ignore_pause, 'Ignore Pause',
"Ignore calls to pause_before_teardown")
_add_boolean('hide_orphans', options.hide_orphans, 'Hide Orphans',
opt_maker.add_boolean('hide_orphans', options.hide_orphans, 'Hide Orphans',
'Do not display orphan counts in output.')
_add_boolean('should_exit', options.should_exit, 'Exit on Finish',
opt_maker.add_boolean('should_exit', options.should_exit, 'Exit on Finish',
"Exit when tests finished.")
_add_boolean('should_exit_on_success', options.should_exit_on_success, 'Exit on Success',
opt_maker.add_boolean('should_exit_on_success', options.should_exit_on_success, 'Exit on Success',
"Exit if there are no failures. Does nothing if 'Exit on Finish' is enabled.")
_add_select('double_strategy', 'Script Only', ['Include Native', 'Script Only'], 'Double Strategy',
opt_maker.add_select('double_strategy', 'Script Only', ['Include Native', 'Script Only'], 'Double Strategy',
'"Include Native" will include native methods in Doubles. "Script Only" will not. ' + "\n" + \
'The native method override warning is disabled when creating Doubles.' + "\n" + \
'This is the default, you can override this at the script level or when creating doubles.')
_cfg_ctrls.double_strategy.value = GutUtils.get_enum_value(
options.double_strategy, GutUtils.DOUBLE_STRATEGY, GutUtils.DOUBLE_STRATEGY.SCRIPT_ONLY)
_add_boolean('errors_cause_failure', !options.errors_do_not_cause_failure, 'Errors cause failures.',
"When GUT generates an error (not an engine error) it causes tests to fail.")
_add_title('Runner Appearance')
hide_this = _add_boolean("gut_on_top", options.gut_on_top, "On Top",
opt_maker.add_title("Fail Error Types")
opt_maker.add_boolean("error_tracking", !options.no_error_tracking, 'Track Errors',
"Enable/Disable GUT's ability to detect engine and push errors.")
opt_maker.add_boolean('engine_errors_cause_failure', options.failure_error_types.has(GutConfig.FAIL_ERROR_TYPE_ENGINE),
'Engine', 'Any script/engine error that occurs during a test will cause the test to fail.')
opt_maker.add_boolean('push_error_errors_cause_failure', options.failure_error_types.has(GutConfig.FAIL_ERROR_TYPE_PUSH_ERROR),
'Push', 'Any error generated by a call to push_error that occurs during a test will cause the test to fail.')
opt_maker.add_boolean('gut_errors_cause_failure', options.failure_error_types.has(GutConfig.FAIL_ERROR_TYPE_GUT),
'GUT', 'Any internal GUT error that occurs while a test is running will cause it to fail..')
opt_maker.add_title('Runner Appearance')
hide_this = opt_maker.add_boolean("gut_on_top", options.gut_on_top, "On Top",
"The GUT Runner appears above children added during tests.")
_add_number('opacity', options.opacity, 'Opacity', 0, 100,
opt_maker.add_number('opacity', options.opacity, 'Opacity', 0, 100,
"The opacity of GUT when tests are running.")
hide_this = _add_boolean('should_maximize', options.should_maximize, 'Maximize',
hide_this = opt_maker.add_boolean('should_maximize', options.should_maximize, 'Maximize',
"Maximize GUT when tests are being run.")
_add_boolean('compact_mode', options.compact_mode, 'Compact Mode',
opt_maker.add_boolean('compact_mode', options.compact_mode, 'Compact Mode',
'The runner will be in compact mode. This overrides Maximize.')
_add_select('font_name', options.font_name, GutUtils.avail_fonts, 'Font',
opt_maker.add_select('font_name', options.font_name, GutUtils.avail_fonts, 'Font',
"The font to use for text output in the Gut Runner.")
_add_number('font_size', options.font_size, 'Font Size', 5, 100,
opt_maker.add_number('font_size', options.font_size, 'Font Size', 5, 100,
"The font size for text output in the Gut Runner.")
hide_this = _add_color('font_color', options.font_color, 'Font Color',
hide_this = opt_maker.add_color('font_color', options.font_color, 'Font Color',
"The font color for text output in the Gut Runner.")
_add_color('background_color', options.background_color, 'Background Color',
opt_maker.add_color('background_color', options.background_color, 'Background Color',
"The background color for text output in the Gut Runner.")
_add_boolean('disable_colors', options.disable_colors, 'Disable Formatting',
opt_maker.add_boolean('disable_colors', options.disable_colors, 'Disable Formatting',
'Disable formatting and colors used in the Runner. Does not affect panel output.')
_titles.dirs = _add_title('Test Directories')
_add_boolean('include_subdirs', options.include_subdirs, 'Include Subdirs',
_titles.dirs = opt_maker.add_title('Test Directories')
opt_maker.add_boolean('include_subdirs', options.include_subdirs, 'Include Subdirs',
"Include subdirectories of the directories configured below.")
var dirs_to_load = options.configured_dirs
@ -250,40 +181,35 @@ func set_options(opts):
if(dirs_to_load.size() > i):
value = dirs_to_load[i]
var test_dir = _add_directory(str('directory_', i), value, str(i))
var test_dir = opt_maker.add_directory(str('directory_', i), value, str(i))
test_dir.enabled_button.visible = true
test_dir.enabled_button.button_pressed = options.dirs.has(value)
_add_title("XML Output")
_add_save_file_anywhere("junit_xml_file", options.junit_xml_file, "Output Path",
"Path3D and filename where GUT should create a JUnit compliant XML file. " +
opt_maker.add_title("XML Output")
opt_maker.add_save_file_anywhere("junit_xml_file", options.junit_xml_file, "Output Path",
"Path and filename where GUT should create a JUnit compliant XML file. " +
"This file will contain the results of the last test run. To avoid " +
"overriding the file use Include Timestamp.")
_add_boolean("junit_xml_timestamp", options.junit_xml_timestamp, "Include Timestamp",
opt_maker.add_boolean("junit_xml_timestamp", options.junit_xml_timestamp, "Include Timestamp",
"Include a timestamp in the filename so that each run gets its own xml file.")
_add_title('Hooks')
_add_file('pre_run_script', options.pre_run_script, 'Pre-Run Hook',
opt_maker.add_title('Hooks')
opt_maker.add_file('pre_run_script', options.pre_run_script, 'Pre-Run Hook',
'This script will be run by GUT before any tests are run.')
_add_file('post_run_script', options.post_run_script, 'Post-Run Hook',
opt_maker.add_file('post_run_script', options.post_run_script, 'Post-Run Hook',
'This script will be run by GUT after all tests are run.')
_add_title('Misc')
_add_value('prefix', options.prefix, 'Script Prefix',
opt_maker.add_title('Misc')
opt_maker.add_value('prefix', options.prefix, 'Script Prefix',
"The filename prefix for all test scripts.")
_add_value('suffix', options.suffix, 'Script Suffix',
opt_maker.add_value('suffix', options.suffix, 'Script Suffix',
"Script suffix, including .gd extension. For example '_foo.gd'.")
_add_number('paint_after', options.paint_after, 'Paint After', 0.0, 1.0,
opt_maker.add_float('paint_after', options.paint_after, 'Paint After', .05, 0.0, 1.0,
"How long GUT will wait before pausing for 1 frame to paint the screen. 0 is never.")
# since _add_number doesn't set step property, it will default to a step of
# 1 and cast values to int. Give it a .5 step and re-set the value.
_cfg_ctrls.paint_after.value_ctrl.step = .05
_cfg_ctrls.paint_after.value = options.paint_after
func get_options(base_opts):
@ -291,13 +217,12 @@ func get_options(base_opts):
# Settings
to_return.log_level = _cfg_ctrls.log_level.value
to_return.wait_log_delay = _cfg_ctrls.wait_log_delay.value
to_return.ignore_pause = _cfg_ctrls.ignore_pause.value
to_return.hide_orphans = _cfg_ctrls.hide_orphans.value
to_return.should_exit = _cfg_ctrls.should_exit.value
to_return.should_exit_on_success = _cfg_ctrls.should_exit_on_success.value
to_return.double_strategy = _cfg_ctrls.double_strategy.value
to_return.errors_do_not_cause_failure = !_cfg_ctrls.errors_cause_failure.value
# Runner Appearance
to_return.font_name = _cfg_ctrls.font_name.text
@ -311,6 +236,17 @@ func get_options(base_opts):
to_return.gut_on_top = _cfg_ctrls.gut_on_top.value
to_return.paint_after = _cfg_ctrls.paint_after.value
# Fail Error Types
to_return.no_error_tracking = !_cfg_ctrls.error_tracking
var fail_error_types = []
if(_cfg_ctrls.engine_errors_cause_failure.value):
fail_error_types.append(GutConfig.FAIL_ERROR_TYPE_ENGINE)
if(_cfg_ctrls.push_error_errors_cause_failure.value):
fail_error_types.append(GutConfig.FAIL_ERROR_TYPE_PUSH_ERROR)
if(_cfg_ctrls.gut_errors_cause_failure.value):
fail_error_types.append(GutConfig.FAIL_ERROR_TYPE_GUT)
to_return.failure_error_types = fail_error_types
# Directories
to_return.include_subdirs = _cfg_ctrls.include_subdirs.value

View file

@ -1 +1 @@
uid://xlv525p30gpr
uid://chosc1tvfaduq

View file

@ -0,0 +1,37 @@
extends EditorDock
var _panel : Control = null
var _current_layout = -1
func _update_layout(layout):
_current_layout = layout
if(_panel != null):
if(layout == DOCK_LAYOUT_FLOATING):
_windowed_mode()
else:
_dock_mode()
# -------------
# Private
# -------------
func _windowed_mode():
_panel.show_layout_buttons(true)
func _dock_mode():
_panel.results_horiz_layout()
_panel.show_layout_buttons(false)
# -------------
# Public
# -------------
func add_bottom_panel(gut_bottom_panel):
_panel = gut_bottom_panel
# Make floating button not supported right now
add_child(_panel)
_panel.make_floating_btn.visible = false
_update_layout(_current_layout)

View file

@ -0,0 +1 @@
uid://cmj50k82tckur

View file

@ -24,6 +24,7 @@ var _ctrls = {
time_label = null,
title = null,
title_bar = null,
tgl_word_wrap = null, # optional
}
var _title_mouse = {
@ -42,6 +43,8 @@ func _ready():
_ctrls.btn_continue.pressed.connect(_on_continue_pressed)
_ctrls.switch_modes.pressed.connect(_on_switch_modes_pressed)
_ctrls.title_bar.gui_input.connect(_on_title_bar_input)
if(_ctrls.tgl_word_wrap != null):
_ctrls.tgl_word_wrap.toggled.connect(_on_word_wrap_toggled)
_ctrls.prog_script.value = 0
_ctrls.prog_test.value = 0
@ -79,6 +82,7 @@ func _populate_ctrls():
_ctrls.time_label = _get_first_child_named('TimeLabel', self)
_ctrls.title = _get_first_child_named("Title", self)
_ctrls.title_bar = _get_first_child_named("TitleBar", self)
_ctrls.tgl_word_wrap = _get_first_child_named("WordWrap", self)
func _get_first_child_named(obj_name, parent_obj):
@ -96,7 +100,7 @@ func _get_first_child_named(obj_name, parent_obj):
to_return = _get_first_child_named(obj_name, kids[index])
if(to_return == null):
index += 1
return to_return
@ -157,6 +161,9 @@ func _on_gut_end_pause():
func _on_switch_modes_pressed():
switch_modes.emit()
func _on_word_wrap_toggled(toggled):
_ctrls.rtl.autowrap_mode = toggled
# ------------------
# Public
# ------------------

View file

@ -1 +1 @@
uid://xsm23flin7t3
uid://blvhsbnsvfyow

225
addons/gut/gui/gut_logo.gd Normal file
View file

@ -0,0 +1,225 @@
@tool
extends Node2D
class Eyeball:
extends Node2D
var _should_draw_laser = false
var _laser_end_pos = Vector2.ZERO
var _laser_timer : Timer = null
var _color_tween : Tween
var _size_tween : Tween
var sprite : Sprite2D = null
var default_position = Vector2(0, 0)
var move_radius = 25
var move_center = Vector2(0, 0)
var default_color = Color(0.31, 0.31, 0.31)
var _color = default_color :
set(val):
_color = val
queue_redraw()
var color = _color :
set(val):
_start_color_tween(_color, val)
get(): return _color
var default_size = 70
var _size = default_size :
set(val):
_size = val
queue_redraw()
var size = _size :
set(val):
_start_size_tween(_size, val)
get(): return _size
func _init(node):
sprite = node
default_position = sprite.position
move_center = sprite.position
# hijack the original sprite, because I want to draw it here but keep
# the original in the scene for layout.
position = sprite.position
sprite.get_parent().add_child(self)
sprite.visible = false
func _ready():
_laser_timer = Timer.new()
_laser_timer.wait_time = .1
_laser_timer.one_shot = true
add_child(_laser_timer)
_laser_timer.timeout.connect(func(): _should_draw_laser = false)
func _process(_delta):
if(_should_draw_laser):
queue_redraw()
func _start_color_tween(old_color, new_color):
if(_color_tween != null and _color_tween.is_running()):
_color_tween.kill()
_color_tween = create_tween()
_color_tween.tween_property(self, '_color', new_color, .3).from(old_color)
_color_tween.play()
func _start_size_tween(old_size, new_size):
if(_size_tween != null and _size_tween.is_running()):
_size_tween.kill()
_size_tween = create_tween()
_size_tween.tween_property(self, '_size', new_size, .3).from(old_size)
_size_tween.play()
var _laser_size = 20.0
func _draw() -> void:
draw_circle(Vector2.ZERO, size, color, true, -1, true)
if(_should_draw_laser):
var end_pos = (_laser_end_pos - global_position) * 2
var laser_size = _laser_size * (float(size)/float(default_size))
draw_line(Vector2.ZERO, end_pos, color, laser_size)
draw_line(Vector2.ZERO, end_pos, Color(1, 1, 1, .5), laser_size * .8)
# There's a bug in here where the eye shakes like crazy. It's a feature
# now. Don't fix it.
func look_at_local_position(local_pos):
var dir = position.direction_to(local_pos)
var dist = position.distance_to(local_pos)
position = move_center + (dir * min(dist, move_radius))
position.x = clamp(position.x, move_center.x - move_radius, move_center.x + move_radius)
position.y = clamp(position.y, move_center.y - move_radius, move_center.y + move_radius)
func reset():
color = default_color
size = default_size
func eye_laser(global_pos):
_should_draw_laser = true
_laser_end_pos = global_pos
_laser_timer.start()
func _stop_laser():
_should_draw_laser = false
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
var GutEditorGlobals = load('res://addons/gut/gui/editor_globals.gd')
# Active means it's actively doing stuff. When this is not active the eyes
# won't follow, but you can still make the sizes change by calling methods on
# this.
@export var active = false :
set(val):
active = val
if(!active and is_inside_tree()):
left_eye.position = left_eye.default_position
right_eye.position = right_eye.default_position
# When disabled, this will reset to default and you can't make it do anything.
@export var disabled = false :
set(val):
disabled = val
if(disabled and is_inside_tree()):
left_eye.position = left_eye.default_position
right_eye.position = right_eye.default_position
left_eye.reset()
right_eye.reset()
modulate = Color.GRAY
$BaseLogo.texture = _no_shine
else:
$BaseLogo.texture = _normal
modulate = Color.WHITE
@onready var _reset_timer = $ResetTimer
@onready var _face_button = $FaceButton
@onready var left_eye : Eyeball = Eyeball.new($BaseLogo/LeftEye)
@onready var right_eye : Eyeball = Eyeball.new($BaseLogo/RightEye)
var _no_shine = load("res://addons/gut/images/GutIconV2_no_shine.png")
var _normal = load("res://addons/gut/images/GutIconV2_base.png")
var _is_in_edited_scene = false
signal pressed
func _debug_ready():
position = Vector2(500, 500)
active = true
func _ready():
_is_in_edited_scene = GutEditorGlobals.is_being_edited_in_editor(self)
if(get_parent() == get_tree().root):
_debug_ready()
disabled = disabled
active = active
left_eye.move_center.x -= 20
right_eye.move_center.x += 10
_face_button.modulate.a = 0.0
func _process(_delta):
if(active and !disabled and !_is_in_edited_scene):
left_eye.look_at_local_position(get_local_mouse_position())
right_eye.look_at_local_position(get_local_mouse_position())
# ----------------
# Events
# ----------------
func _on_reset_timer_timeout() -> void:
left_eye.reset()
right_eye.reset()
func _on_face_button_pressed() -> void:
pressed.emit()
# ----------------
# Public
# ----------------
func set_eye_scale(left, right=left):
if(disabled or _is_in_edited_scene):
return
left_eye.size = left_eye.default_size * left
right_eye.size = right_eye.default_size * right
_reset_timer.start()
func reset_eye_size():
if(disabled or _is_in_edited_scene):
return
left_eye.size = left_eye.default_size
right_eye.size = right_eye.default_size
func set_eye_color(left, right=left):
if(disabled or _is_in_edited_scene):
return
left_eye.color = left
right_eye.color = right
_reset_timer.start()
func reset_eye_color():
if(disabled or _is_in_edited_scene):
return
left_eye.color = left_eye.default_color
right_eye.color = right_eye.default_color
# I removed the eye lasers because they aren't ready yet. I've already spent
# too much time on this logo. It's great, I love it...but it's been long
# enough. This gives me, or someone else, something to do later.
#func eye_lasers(global_pos):
#left_eye.eye_laser(global_pos)
#right_eye.eye_laser(global_pos)

View file

@ -0,0 +1 @@
uid://b8lvgepb64m8t

View file

@ -37,12 +37,11 @@ var hide_result_tree = null
var hide_output_text = null
var hide_settings = null
var use_colors = null # ? might be output panel
# var shortcut_run_all = null
# var shortcut_run_current_script = null
# var shortcut_run_current_inner = null
# var shortcut_run_current_test = null
# var shortcut_panel_button = null
var run_externally = null
var run_externally_options_dialog_size = null
var shortcuts_dialog_size = null
var gut_window_size = null
var gut_window_on_top = null
func _init(editor_settings):
@ -52,12 +51,12 @@ func _init(editor_settings):
hide_output_text = GutEditorPref.new('hide_output_text', false, editor_settings)
hide_settings = GutEditorPref.new('hide_settings', false, editor_settings)
use_colors = GutEditorPref.new('use_colors', true, editor_settings)
run_externally = GutEditorPref.new('run_externally', false, editor_settings)
run_externally_options_dialog_size = GutEditorPref.new('run_externally_options_dialog_size', Vector2i(-1, -1), editor_settings)
shortcuts_dialog_size = GutEditorPref.new('shortcuts_dialog_size', Vector2i(-1, -1), editor_settings)
gut_window_size = GutEditorPref.new('editor_window_size', Vector2i(-1, -1), editor_settings)
gut_window_on_top = GutEditorPref.new('editor_window_on_top', false, editor_settings)
# shortcut_run_all = GutEditorPref.new('shortcut_run_all', EMPTY, editor_settings)
# shortcut_run_current_script = GutEditorPref.new('shortcut_run_current_script', EMPTY, editor_settings)
# shortcut_run_current_inner = GutEditorPref.new('shortcut_run_current_inner', EMPTY, editor_settings)
# shortcut_run_current_test = GutEditorPref.new('shortcut_run_current_test', EMPTY, editor_settings)
# shortcut_panel_button = GutEditorPref.new('shortcut_panel_button', EMPTY, editor_settings)
func save_it():
for prop in get_property_list():
@ -77,4 +76,4 @@ func erase_all():
for prop in get_property_list():
var val = get(prop.name)
if(val is GutEditorPref):
val.erase()
val.erase()

View file

@ -1 +1 @@
uid://3n0bh8lrcprr
uid://dsndkn6whyiov

View file

@ -0,0 +1,124 @@
var PanelControls = load("res://addons/gut/gui/panel_controls.gd")
# All titles so we can free them when we want.
var _all_titles = []
var base_container = null
# All the various PanelControls indexed by thier keys.
var controls = {}
func _init(cont):
base_container = cont
func add_title(text):
var row = PanelControls.BaseGutPanelControl.new(text, text)
base_container.add_child(row)
row.connect('draw', _on_title_cell_draw.bind(row))
_all_titles.append(row)
return row
func add_ctrl(key, ctrl):
controls[key] = ctrl
base_container.add_child(ctrl)
func add_number(key, value, disp_text, v_min, v_max, hint=''):
var ctrl = PanelControls.NumberControl.new(disp_text, value, v_min, v_max, hint)
add_ctrl(key, ctrl)
return ctrl
func add_float(key, value, disp_text, step, v_min, v_max, hint=''):
var ctrl = PanelControls.FloatControl.new(disp_text, value, step, v_min, v_max, hint)
add_ctrl(key, ctrl)
return ctrl
func add_select(key, value, values, disp_text, hint=''):
var ctrl = PanelControls.SelectControl.new(disp_text, value, values, hint)
add_ctrl(key, ctrl)
return ctrl
func add_value(key, value, disp_text, hint=''):
var ctrl = PanelControls.StringControl.new(disp_text, value, hint)
add_ctrl(key, ctrl)
return ctrl
func add_multiline_text(key, value, disp_text, hint=''):
var ctrl = PanelControls.MultiLineStringControl.new(disp_text, value, hint)
add_ctrl(key, ctrl)
return ctrl
func add_boolean(key, value, disp_text, hint=''):
var ctrl = PanelControls.BooleanControl.new(disp_text, value, hint)
add_ctrl(key, ctrl)
return ctrl
func add_directory(key, value, disp_text, hint=''):
var ctrl = PanelControls.DirectoryControl.new(disp_text, value, hint)
add_ctrl(key, ctrl)
ctrl.dialog.title = disp_text
return ctrl
func add_file(key, value, disp_text, hint=''):
var ctrl = PanelControls.DirectoryControl.new(disp_text, value, hint)
add_ctrl(key, ctrl)
ctrl.dialog.file_mode = ctrl.dialog.FILE_MODE_OPEN_FILE
ctrl.dialog.title = disp_text
return ctrl
func add_save_file_anywhere(key, value, disp_text, hint=''):
var ctrl = PanelControls.DirectoryControl.new(disp_text, value, hint)
add_ctrl(key, ctrl)
ctrl.dialog.file_mode = ctrl.dialog.FILE_MODE_SAVE_FILE
ctrl.dialog.access = ctrl.dialog.ACCESS_FILESYSTEM
ctrl.dialog.title = disp_text
return ctrl
func add_color(key, value, disp_text, hint=''):
var ctrl = PanelControls.ColorControl.new(disp_text, value, hint)
add_ctrl(key, ctrl)
return ctrl
var _blurbs = 0
func add_blurb(text):
var ctrl = RichTextLabel.new()
ctrl.fit_content = true
ctrl.bbcode_enabled = true
ctrl.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
ctrl.text = text
add_ctrl(str("blurb_", _blurbs), ctrl)
return ctrl
# ------------------
# Events
# ------------------
func _on_title_cell_draw(which):
which.draw_rect(Rect2(Vector2(0, 0), which.size), Color(0, 0, 0, .15))
# ------------------
# Public
# ------------------
func clear():
for key in controls:
controls[key].free()
controls.clear()
for entry in _all_titles:
entry.free()
_all_titles.clear()

View file

@ -0,0 +1 @@
uid://bjahqsqo645sf

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