diff --git a/.gutconfig.json b/.gutconfig.json
index 71b7284e..22202ad1 100644
--- a/.gutconfig.json
+++ b/.gutconfig.json
@@ -3,6 +3,6 @@
"should_maximize":false,
"should_exit":true,
"ignore_pause":true,
- "log": 1,
+ "log": 2,
"opacity":100
}
diff --git a/CHANGES.md b/CHANGES.md
index fd1994ce..4dd145a3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,6 +2,23 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
+# 6.5.0
+
+## Fixes
+* Bug fix by __Xrayez__ to ensure that the command line tool always sets the return code properly. Before it was only setting it if Gut was configured to exit when done.
+* Fixed an issue where the command line tool wasn't setting options correctly when the .gutconfig.json file was present. All options are now applied correctly based on order of precedence (default < gutconfig < cmd line). I also added the `-gpo` command line option to print out all option values from all sources and what value would be used when running Gut. This will make debugging theses issues easier later.
+
+## Features
+* We have two new asserts thanks to __hbergren__. These asserts make it easier to assert if a value is within or outside of a +/- range of a value. These are especially useful when comparing floats that the engine insists aren't equal due to rounding errors.
+ * `assert_almost_eq(got, expected, error_interval, text='')` - Asserts that `got` is within the range of `expected` +/- `error_interval`. The upper and lower bounds are included in the check. Verified to work with integers, floats, and Vector2. Should work with anything that can be added/subtracted. Examples
+ * `assert_almost_ne(got, expected, error_interval, text='')` - This is the inverse of `assert_almost_eq`. This will pass if `got` is outside the range of `expected` +/- `error_interval`. Examples
+* __Xrayez__ contributed a new option to maximize the Gut window upon launch. The option can be set in the editor, .gutconfig, or at the command line.
+* Added the `-gpo` command line option to print out all option values from all sources and what value would be used when running Gut. This will make debugging option issues much easier.
+
+
+## Other
+Some housekeeping. Removed some commented out and unreachable code. Renamed a lot of tests in `test_test.gd` since it now uses Inner Test Classes which allows for better names. They were setting a bad example for PRs.
+
# 6.4.0
I've "dog food"ed the doubles, stubs, and spies more in my own game and I think they are pretty stable. This release contains some tweaks to doubles and stubs and the introduction of spies as well as some other testing goodness.
diff --git a/README.md b/README.md
index f7de7316..bc906c1a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# [The readme has moved to a wiki.](https://github.com/bitwes/Gut/wiki)
-# Gut 6.4.0
+# Gut 6.5.0
GUT (Godot Unit Test) is a utility for writing tests for your Godot Engine game. It allows you to write tests for your gdscript in gdscript.
More info can be found in the [wiki](https://github.com/bitwes/Gut/wiki).
diff --git a/addons/gut/gut.gd b/addons/gut/gut.gd
index 592307b3..b3f275d0 100644
--- a/addons/gut/gut.gd
+++ b/addons/gut/gut.gd
@@ -28,7 +28,7 @@
################################################################################
# View readme for usage details.
#
-# Version 6.4.0
+# Version 6.5.0
################################################################################
extends "res://addons/gut/gut_gui.gd"
diff --git a/addons/gut/gut_cmdln.gd b/addons/gut/gut_cmdln.gd
index 25bc485a..e36dd48c 100644
--- a/addons/gut/gut_cmdln.gd
+++ b/addons/gut/gut_cmdln.gd
@@ -37,7 +37,7 @@
# See the readme for a list of options and examples. You can also use the -gh
# option to get more information about how to use the command line interface.
#
-# Version 6.4.0
+# Version 6.5.0
################################################################################
extends SceneTree
@@ -96,10 +96,11 @@ class CmdLineParser:
return to_return
- # returns the value of an option if it was specfied, otherwise
- # it returns the default.
- func get_value(option, default):
- var to_return = default
+ # returns the value of an option if it was specfied, null otherwise. This
+ # used to return the default but that became problemnatic when trying to
+ # punch layer the different places where values could be specified.
+ func get_value(option):
+ var to_return = null
var opt_loc = find_option(option)
if(opt_loc != -1):
to_return = get_option_value(_opts[opt_loc])
@@ -109,11 +110,7 @@ class CmdLineParser:
# returns true if it finds the option, false if not.
func was_specified(option):
- var opt_loc = find_option(option)
- if(opt_loc != -1):
- _opts.remove(opt_loc)
-
- return opt_loc != -1
+ return find_option(option) != -1
#-------------------------------------------------------------------------------
# Simple class to hold a command line option
@@ -128,7 +125,7 @@ class Option:
option_name = name
default = default_value
description = desc
- value = default_value
+ value = null#default_value
func pad(value, size, pad_with=' '):
var to_return = value
@@ -143,7 +140,6 @@ class Option:
subbed_desc = subbed_desc.replace('[default]', str(default))
return pad(option_name, min_space) + subbed_desc
-
#-------------------------------------------------------------------------------
# The high level interface between this script and the command line options
# supplied. Uses Option class and CmdLineParser to extract information from
@@ -199,19 +195,90 @@ class Options:
for i in range(options.size()):
var t = typeof(options[i].default)
- if(t == TYPE_INT):
- options[i].value = int(parser.get_value(options[i].option_name, options[i].default))
- elif(t == TYPE_STRING):
- options[i].value = parser.get_value(options[i].option_name, options[i].default)
- elif(t == TYPE_ARRAY):
- options[i].value = parser.get_array_value(options[i].option_name)
- elif(t == TYPE_BOOL):
- options[i].value = parser.was_specified(options[i].option_name)
- elif(t == TYPE_NIL):
- print(options[i].option_name + ' cannot be processed, it has a nil datatype')
- else:
- print(options[i].option_name + ' cannot be processsed, it has unknown datatype:' + str(t))
+ # only set values that were specified at the command line so that
+ # we can punch through default and config values correctly later.
+ # Without this check, you can't tell the difference between the
+ # defaults and what was specified, so you can't punch through
+ # higher level options.
+ if(parser.was_specified(options[i].option_name)):
+ if(t == TYPE_INT):
+ options[i].value = int(parser.get_value(options[i].option_name))
+ elif(t == TYPE_STRING):
+ options[i].value = parser.get_value(options[i].option_name)
+ elif(t == TYPE_ARRAY):
+ options[i].value = parser.get_array_value(options[i].option_name)
+ elif(t == TYPE_BOOL):
+ options[i].value = parser.was_specified(options[i].option_name)
+ elif(t == TYPE_NIL):
+ print(options[i].option_name + ' cannot be processed, it has a nil datatype')
+ else:
+ print(options[i].option_name + ' cannot be processsed, it has unknown datatype:' + str(t))
+
+#-------------------------------------------------------------------------------
+# Helper class to resolve the various different places where an option can
+# be set. Using the get_value method will enforce the order of precedence of:
+# 1. command line value
+# 2. config file value
+# 3. default value
+#
+# The idea is that you set the base_opts. That will get you a copies of the
+# hash with null values for the other types of values. Lower precendeted hashes
+# will punch through null values of higher precedednted hashes.
+#-------------------------------------------------------------------------------
+class OptionResolver:
+ var base_opts = null
+ var cmd_opts = null
+ var config_opts = null
+
+
+ func get_value(key):
+ return _nvl(cmd_opts[key], _nvl(config_opts[key], base_opts[key]))
+
+ func set_base_opts(opts):
+ base_opts = opts
+ cmd_opts = _null_copy(opts)
+ config_opts = _null_copy(opts)
+ func _null_copy(h):
+ var new_hash = {}
+ for key in h:
+ new_hash[key] = null
+ return new_hash
+
+ func _nvl(a, b):
+ if(a == null):
+ return b
+ else:
+ return a
+ func _string_it(h):
+ var to_return = ''
+ for key in h:
+ to_return += str('(',key, ':', _nvl(h[key], 'NULL'), ')')
+ return to_return
+
+ func to_s():
+ return str("base:\n", _string_it(base_opts), "\n", \
+ "config:\n", _string_it(config_opts), "\n", \
+ "cmd:\n", _string_it(cmd_opts), "\n", \
+ "resolved:\n", _string_it(get_resolved_values()))
+
+ func get_resolved_values():
+ var to_return = {}
+ for key in base_opts:
+ to_return[key] = get_value(key)
+ return to_return
+
+ func to_s_verbose():
+ var to_return = ''
+ var resolved = get_resolved_values()
+ for key in base_opts:
+ to_return += str(key, "\n")
+ to_return += str(' default: ', _nvl(base_opts[key], 'NULL'), "\n")
+ to_return += str(' config: ', _nvl(config_opts[key], 'NULL'), "\n")
+ to_return += str(' cmd: ', _nvl(cmd_opts[key], 'NULL'), "\n")
+ to_return += str(' final: ', _nvl(resolved[key], 'NULL'), "\n")
+
+ return to_return
#-------------------------------------------------------------------------------
# Here starts the actual script that uses the Options class to kick off Gut
@@ -233,12 +300,12 @@ var options = {
tests = [],
dirs = [],
selected = '',
- prefix = '',
- suffix = '',
- gut_location = '',
+ prefix = 'test_',
+ suffix = '.gd',
+ gut_location = 'res://addons/gut/gut.gd',
unit_test_name = '',
show_help = false,
- config_file = '',
+ config_file = 'res://.gutconfig.json',
inner_class = '',
opacity = 100
}
@@ -277,26 +344,28 @@ func setup_options():
opts.add('-gconfig', 'res://.gutconfig.json', 'A config file that contains configuration information. Default is res://.gutconfig.json')
opts.add('-ginner_class', '', 'Only run inner classes that contain this string')
opts.add('-gopacity', 100, 'Set opacity of test runner window. Use range 0 - 100. 0 = transparent, 100 = opaque.')
+ opts.add('-gpo', false, 'Print option values from all sources and the value used, then quit.')
return opts
# Parses options, applying them to the _tester or setting values
# in the options struct.
-func extract_options(opt):
- options.tests = opt.get_value('-gtest')
- options.dirs = opt.get_value('-gdir')
- options.should_maximize = opt.get_value('-gmaximize')
- options.should_exit = opt.get_value('-gexit')
- options.log_level = opt.get_value('-glog')
- options.ignore_pause_before_teardown = opt.get_value('-gignore_pause')
- options.selected = opt.get_value('-gselect')
- options.prefix = opt.get_value('-gprefix')
- options.suffix = opt.get_value('-gsuffix')
- options.gut_location = opt.get_value('-gutloc')
- options.unit_test_name = opt.get_value('-gunit_test_name')
- options.config_file = opt.get_value('-gconfig')
- options.inner_class = opt.get_value('-ginner_class')
- options.opacity = opt.get_value('-gopacity')
+
+func extract_command_line_options(from, to):
+ to.tests = from.get_value('-gtest')
+ to.dirs = from.get_value('-gdir')
+ to.should_exit = from.get_value('-gexit')
+ to.should_maximize = from.get_value('-gmaximize')
+ to.log_level = from.get_value('-glog')
+ to.ignore_pause_before_teardown = from.get_value('-gignore_pause')
+ to.selected = from.get_value('-gselect')
+ to.prefix = from.get_value('-gprefix')
+ to.suffix = from.get_value('-gsuffix')
+ to.gut_location = from.get_value('-gutloc')
+ to.unit_test_name = from.get_value('-gunit_test_name')
+ to.config_file = from.get_value('-gconfig')
+ to.inner_class = from.get_value('-ginner_class')
+ to.opacity = from.get_value('-gopacity')
func get_value(dict, index, default):
if(dict.has(index)):
@@ -304,7 +373,7 @@ func get_value(dict, index, default):
else:
return default
-func load_options_from_config_file(file_path):
+func load_options_from_config_file(file_path, into):
# SHORTCIRCUIT
var f = File.new()
if(!f.file_exists(file_path)):
@@ -326,47 +395,46 @@ func load_options_from_config_file(file_path):
print(' ', results.error_string)
return -1
- options.dirs = get_value(results.result, 'dirs', [])
- options.should_maximize = get_value(results.result, 'should_maximize', false)
- options.should_exit = get_value(results.result, 'should_exit', false)
- options.ignore_pause_before_teardown = get_value(results.result, 'ignore_pause', false)
- options.log_level = get_value(results.result, 'log', 1)
- options.inner_class = get_value(results.result, 'inner_class', '')
- options.opacity = get_value(results.result, 'opacity', 100)
+ into.dirs = get_value(results.result, 'dirs', [])
+ into.should_maximize = get_value(results.result, 'should_maximize', false)
+ into.should_exit = get_value(results.result, 'should_exit', false)
+ into.ignore_pause_before_teardown = get_value(results.result, 'ignore_pause', false)
+ into.log_level = get_value(results.result, 'log', 1)
+ into.inner_class = get_value(results.result, 'inner_class', '')
+ into.opacity = get_value(results.result, 'opacity', 100)
return 1
# apply all the options specified to _tester
-func apply_options():
- # setup the tester
- _tester = load(options.gut_location).new()
+func apply_options(opts):
+ _tester = load(opts.gut_location).new()
get_root().add_child(_tester)
- _tester.connect('tests_finished', self, '_on_tests_finished')
+ _tester.connect('tests_finished', self, '_on_tests_finished', [opts.should_exit])
_tester.set_yield_between_tests(true)
- _tester.set_modulate(Color(1.0, 1.0, 1.0, min(1.0, float(options.opacity) / 100)))
+ _tester.set_modulate(Color(1.0, 1.0, 1.0, min(1.0, float(opts.opacity) / 100)))
_tester.show()
- if(options.should_maximize):
+ if(opts.should_maximize):
_tester.maximize()
- if(options.inner_class != ''):
- _tester.set_inner_class_name(options.inner_class)
- _tester.set_log_level(options.log_level)
- _tester.set_ignore_pause_before_teardown(options.ignore_pause_before_teardown)
+ if(opts.inner_class != ''):
+ _tester.set_inner_class_name(opts.inner_class)
+ _tester.set_log_level(opts.log_level)
+ _tester.set_ignore_pause_before_teardown(opts.ignore_pause_before_teardown)
- for i in range(options.dirs.size()):
- _tester.add_directory(options.dirs[i], options.prefix, options.suffix)
+ for i in range(opts.dirs.size()):
+ _tester.add_directory(opts.dirs[i], opts.prefix, opts.suffix)
- for i in range(options.tests.size()):
- _tester.add_script(options.tests[i])
+ for i in range(opts.tests.size()):
+ _tester.add_script(opts.tests[i])
- if(options.selected != ''):
- _auto_run = _tester.select_script(options.selected)
+ if(opts.selected != ''):
+ _auto_run = _tester.select_script(opts.selected)
_run_single = true
if(!_auto_run):
- _tester.p("Could not find a script that matched: " + options.selected)
+ _tester.p("Could not find a script that matched: " + opts.selected)
- _tester.set_unit_test_name(options.unit_test_name)
+ _tester.set_unit_test_name(opts.unit_test_name)
# Loads any scripts that have been configured to be loaded through the project
# settings->autoload.
@@ -381,32 +449,40 @@ func load_auto_load_scripts():
obj.set_name(key)
get_root().add_child(obj)
-# parse option and run Gut
+# parse options and run Gut
func _init():
+ var opt_resolver = OptionResolver.new()
+ opt_resolver.set_base_opts(options)
+
print("\n\n", ' --- Gut ---')
var o = setup_options()
o.parse()
- extract_options(o)
- var load_result = load_options_from_config_file(options.config_file)
+ extract_command_line_options(o, opt_resolver.cmd_opts)
+ var load_result = \
+ load_options_from_config_file(options.config_file, opt_resolver.config_opts)
- if(load_result == -1):
+ if(load_result == -1): # -1 indicates json parse error
quit()
else:
if(o.get_value('-gh')):
o.print_help()
quit()
+ elif(o.get_value('-gpo')):
+ print('All command line options and where they are specified. ' +
+ 'The "final" value shows which value will actually be used ' +
+ 'based on order of precedence (default < .gutconfig < cmd line).' + "\n")
+ print(opt_resolver.to_s_verbose())
+ quit()
else:
load_auto_load_scripts()
- apply_options()
+ apply_options(opt_resolver.get_resolved_values())
if(_auto_run):
_tester.test_scripts(!_run_single)
# exit if option is set.
-func _on_tests_finished():
- if(options.should_exit):
- if _tester.get_fail_count() == 0:
- OS.exit_code = 0
- else:
- OS.exit_code = 1
+func _on_tests_finished(should_exit):
+ if(_tester.get_fail_count()):
+ OS.exit_code = 1
+ if(should_exit):
quit()
diff --git a/addons/gut/gut_gui.gd b/addons/gut/gut_gui.gd
index a0884f75..252c5222 100644
--- a/addons/gut/gut_gui.gd
+++ b/addons/gut/gut_gui.gd
@@ -44,7 +44,7 @@ var _is_running = false
var min_size = Vector2(650, 400)
var title_offset = Vector2(0, get_constant("title_height"))
-#controls
+# controls
var _ctrls = {
text_box = TextEdit.new(),
run_button = Button.new(),
@@ -85,6 +85,7 @@ func _set_anchor_bottom_left(obj):
obj.set_anchor(MARGIN_TOP, ANCHOR_END)
#-------------------------------------------------------------------------------
+# Adds all controls to the window, sizes and poisitions them.
#-------------------------------------------------------------------------------
func setup_controls():
var button_size = Vector2(75, 35)
@@ -129,7 +130,6 @@ func setup_controls():
add_child(_ctrls.ignore_continue_checkbox)
_ctrls.ignore_continue_checkbox.set_text("Ignore pauses")
- #_ctrls.ignore_continue_checkbox.set_pressed(_ignore_pause_before_teardown)
_ctrls.ignore_continue_checkbox.set_size(Vector2(50, 30))
_ctrls.ignore_continue_checkbox.set_position(Vector2(_ctrls.continue_button.get_position().x, _ctrls.continue_button.get_position().y + _ctrls.continue_button.get_size().y - 5))
_set_anchor_bottom_right(_ctrls.ignore_continue_checkbox)
@@ -148,8 +148,6 @@ func setup_controls():
_ctrls.log_level_slider.set_ticks(3)
_ctrls.log_level_slider.set_ticks_on_borders(true)
_ctrls.log_level_slider.set_step(1)
- #_ctrls.log_level_slider.set_rounded_values(true)
- #_ctrls.log_level_slider.set_value(_log_level)
_set_anchor_bottom_left(_ctrls.log_level_slider)
var script_prog_label = Label.new()
@@ -264,13 +262,13 @@ func _update_controls():
#-------------------------------------------------------------------------------
-#detect mouse movement
+# detect mouse movement
#-------------------------------------------------------------------------------
func _on_mouse_enter():
_mouse_in = true
#-------------------------------------------------------------------------------
-#detect mouse movement
+# detect mouse movement
#-------------------------------------------------------------------------------
func _on_mouse_exit():
_mouse_in = false
@@ -278,7 +276,7 @@ func _on_mouse_exit():
#-------------------------------------------------------------------------------
-#Send text box text to clipboard
+# Send text box text to clipboard
#-------------------------------------------------------------------------------
func _copy_button_pressed():
_ctrls.text_box.select_all()
@@ -294,26 +292,25 @@ func _init_run():
_ctrls.text_box.add_color_region('/#', '#/', Color(.9, .6, 0))
_ctrls.text_box.add_color_region('/-', '-/', Color(1, 1, 0))
_ctrls.text_box.add_color_region('/*', '*/', Color(.5, .5, 1))
- #_ctrls.text_box.set_symbol_color(Color(.5, .5, .5))
_ctrls.runtime_label.set_text('0.0')
_ctrls.test_progress.set_max(1)
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
func _input(event):
- #if the mouse is somewhere within the debug window
+ # if the mouse is somewhere within the debug window
if(_mouse_in):
- #Check for mouse click inside the resize handle
+ # Check for mouse click inside the resize handle
if(event is InputEventMouseButton):
if (event.button_index == 1):
- #It's checking a square area for the bottom right corner, but that's close enough. I'm lazy
+ # It's checking a square area for the bottom right corner, but that's close enough. I'm lazy
if(event.position.x > get_size().x + get_position().x - 10 and event.position.y > get_size().y + get_position().y - 10):
if event.pressed:
_mouse_down = true
_mouse_down_pos = event.position
else:
_mouse_down = false
- #Reszie
+ # Reszie
if(event is InputEventMouseMotion):
if(_mouse_down):
if(get_size() >= min_size):
@@ -335,20 +332,10 @@ func _input(event):
#Custom drawing to indicate results.
#-------------------------------------------------------------------------------
func _draw():
- #Draw the lines in the corner to show where you can
- #drag to resize the dialog
+ # Draw the lines in the corner to show where you can
+ # drag to resize the dialog
var grab_margin = 2
var line_space = 3
var grab_line_color = Color(.4, .4, .4)
for i in range(1, 6):
draw_line(get_size() - Vector2(i * line_space, grab_margin), get_size() - Vector2(grab_margin, i * line_space), grab_line_color)
-
- return
-
- var where = Vector2(430, 565)
- var r = 25
- if(_summary.tests > 0):
- if(_summary.failed > 0):
- draw_circle(where, r , Color(1, 0, 0, 1))
- else:
- draw_circle(where, r, Color(0, 1, 0, 1))
diff --git a/addons/gut/plugin.cfg b/addons/gut/plugin.cfg
index d4b5b563..6d035c3e 100644
--- a/addons/gut/plugin.cfg
+++ b/addons/gut/plugin.cfg
@@ -3,5 +3,5 @@
name="Gut"
description="Unit Testing tool for Godot."
author="Butch Wesley"
-version="6.4.0"
+version="6.5.0"
script="gut_plugin.gd"
diff --git a/addons/gut/test.gd b/addons/gut/test.gd
index e1bf135c..21763cd6 100644
--- a/addons/gut/test.gd
+++ b/addons/gut/test.gd
@@ -237,6 +237,29 @@ func assert_ne(got, not_expected, text=""):
_fail(disp)
else:
_pass(disp)
+
+# ------------------------------------------------------------------------------
+# Asserts that the expected value almost equals the value got.
+# ------------------------------------------------------------------------------
+func assert_almost_eq(got, expected, error_interval, text=''):
+ var disp = "[" + str(got) + "] expected to equal [" + str(expected) + "] +/- [" + str(error_interval) + "]: " + text
+ if(_do_datatypes_match__fail_if_not(got, expected, text) and _do_datatypes_match__fail_if_not(got, error_interval, text)):
+ if(got < (expected - error_interval) or got > (expected + error_interval)):
+ _fail(disp)
+ else:
+ _pass(disp)
+
+# ------------------------------------------------------------------------------
+# Asserts that the expected value does not almost equal the value got.
+# ------------------------------------------------------------------------------
+func assert_almost_ne(got, not_expected, error_interval, text=''):
+ var disp = "[" + str(got) + "] expected to not equal [" + str(not_expected) + "] +/- [" + str(error_interval) + "]: " + text
+ if(_do_datatypes_match__fail_if_not(got, not_expected, text) and _do_datatypes_match__fail_if_not(got, error_interval, text)):
+ if(got < (not_expected - error_interval) or got > (not_expected + error_interval)):
+ _pass(disp)
+ else:
+ _fail(disp)
+
# ------------------------------------------------------------------------------
# Asserts got is greater than expected
# ------------------------------------------------------------------------------
diff --git a/scenes/main.tscn b/scenes/main.tscn
index 9f42fc5d..cbafcf43 100644
--- a/scenes/main.tscn
+++ b/scenes/main.tscn
@@ -5,12 +5,14 @@
[ext_resource path="res://addons/gut/icon.png" type="Texture" id=3]
[node name="main" type="Node2D" index="0"]
+
script = ExtResource( 1 )
__meta__ = {
"__editor_plugin_screen__": "Script"
}
[node name="Gut" type="WindowDialog" parent="." index="0"]
+
visible = false
anchor_left = 0.0
anchor_top = 0.0
@@ -32,7 +34,7 @@ __meta__ = {
"_editor_icon": ExtResource( 3 )
}
_should_maximize = false
-_run_on_load = false
+_run_on_load = true
_select_script = null
_tests_like = null
_inner_class_name = null
@@ -47,12 +49,13 @@ _inner_class_prefix = "Test"
_temp_directory = "user://gut_temp_directory"
_directory1 = "res://test/unit"
_directory2 = "res://test/integration"
-_directory3 = "res://test/samples"
+_directory3 = ""
_directory4 = ""
_directory5 = ""
_directory6 = ""
[node name="RunGutTestsButton" type="Button" parent="." index="1"]
+
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
@@ -77,3 +80,5 @@ flat = false
align = 1
[connection signal="pressed" from="RunGutTestsButton" to="." method="_on_RunGutTestsButton_pressed"]
+
+
diff --git a/test/samples/test_readme_examples.gd b/test/samples/test_readme_examples.gd
index 17e17332..edde8a3e 100644
--- a/test/samples/test_readme_examples.gd
+++ b/test/samples/test_readme_examples.gd
@@ -39,6 +39,39 @@ func test_not_equal():
assert_ne('one', 'one') # FAIL
assert_ne('2', 2) # FAIL
+func test_almost_equals():
+
+ gut.p('-- passing --')
+ assert_almost_eq(0, 1, 1, '0 within range of 1 +/- 1') # PASS
+ assert_almost_eq(2, 1, 1, '2 within range of 1 +/- 1') # PASS
+
+ assert_almost_eq(1.2, 1.0, .5, '1.2 within range of 1 +/- .5') # PASS
+ assert_almost_eq(.5, 1.0, .5, '.5 within range of 1 +/- .5') # PASS
+
+ assert_almost_eq(Vector2(.5, 1.5), Vector2(1.0, 1.0), Vector2(.5, .5)) # PASS
+
+ gut.p('-- failing --')
+ assert_almost_eq(1, 3, 1, '1 outside range of 3 +/- 1') # FAIL
+ assert_almost_eq(2.6, 3.0, .2, '2.6 outside range of 3 +/- .2') # FAIL
+
+ assert_almost_eq(Vector2(.5, 1.5), Vector2(1.0, 1.0), Vector2(.25, .25)) # PASS
+
+func test_almost_not_equals():
+ gut.p('-- passing --')
+ assert_almost_ne(1, 3, 1, '1 outside range of 3 +/- 1') # PASS
+ assert_almost_ne(2.6, 3.0, .2, '2.6 outside range of 3 +/- .2') # PASS
+
+ assert_almost_ne(Vector2(.5, 1.5), Vector2(1.0, 1.0), Vector2(.25, .25)) # PASS
+
+ gut.p('-- failing --')
+ assert_almost_ne(0, 1, 1, '0 within range of 1 +/- 1') # FAIL
+ assert_almost_ne(2, 1, 1, '2 within range of 1 +/- 1') # FAIL
+
+ assert_almost_ne(1.2, 1.0, .5, '1.2 within range of 1 +/- .5') # FAIL
+ assert_almost_ne(.5, 1.0, .5, '.5 within range of 1 +/- .5') # FAIL
+
+ assert_almost_ne(Vector2(.5, 1.5), Vector2(1.0, 1.0), Vector2(.5, .5)) # FAIL
+
func test_greater_than():
var bigger = 5
var smaller = 0
diff --git a/test/unit/test_gut.gd b/test/unit/test_gut.gd
index 7f69fe94..c359d65e 100644
--- a/test/unit/test_gut.gd
+++ b/test/unit/test_gut.gd
@@ -253,6 +253,8 @@ func test_asserts_on_test_object():
pending('This really is not pending')
assert_eq(1, 1, 'text')
assert_ne(1, 2, 'text')
+ assert_almost_eq(5, 5, 0, 'text')
+ assert_almost_ne(5, 6, 0, 'text')
assert_gt(10, 5, 'text')
assert_lt(1, 2, 'text')
assert_true(true, 'text')
diff --git a/test/unit/test_test.gd b/test/unit/test_test.gd
index eaef730a..23f4d26d 100644
--- a/test/unit/test_test.gd
+++ b/test/unit/test_test.gd
@@ -1,6 +1,14 @@
# ------------------------------------------------------------------------------
# Tests test.gd. test.gd contains all the asserts is the class that all
# test scripts inherit from.
+#
+# NOTE on naming tests. Most of these tests were made before Inner Test Classes
+# were supported. To that end a lot of tests should be renamed. All new tests
+# should be in an Inner Test Class and follow a convention similar to:
+# * test_passes_when...
+# * test_passes_if...
+# * test_fails_when...
+# * etc
# ------------------------------------------------------------------------------
extends "res://addons/gut/test.gd"
@@ -62,7 +70,6 @@ class BaseTestClass:
func teardown():
gr.test_with_gut.gut.get_doubler().clear_output_directory()
- #gr.test_with_gut.gut.get_doubler().clear()
gr.test_with_gut.gut.get_spy().clear()
gr.test.free()
@@ -79,94 +86,179 @@ class TestMiscTests:
class TestAssertEq:
extends BaseTestClass
- func test_assert_eq_number_not_equal():
+ func test_passes_when_integer_equal():
+ gr.test.assert_eq(1, 1)
+ assert_pass(gr.test)
+
+ func test_fails_when_number_not_equal():
gr.test.assert_eq(1, 2)
assert_fail(gr.test, 1, "Should fail. 1 != 2")
- func test_assert_eq_number_equal():
- gr.test.assert_eq('asdf', 'asdf')
- assert_pass(gr.test, 1, "Should pass")
-
- func test_float_eq():
+ func test_passes_when_float_eq():
gr.test.assert_eq(1.0, 1.0)
assert_pass(gr.test)
- func test_float_eq_fail():
+ func test_fails_when_float_eq_fail():
gr.test.assert_eq(.19, 1.9)
assert_fail(gr.test)
- func test_cast_float_eq_pass():
+ func test_passes_when_cast_char_to_float():
gr.test.assert_eq(float('0.92'), 0.92)
assert_pass(gr.test, 1, 'I suspect this is failing due to an engine bug.')
- func test_fail_compare_float_cast_as_int():
+ func test_fails_when_comparing_float_cast_as_int():
# int cast will make it 0
gr.test.assert_eq(int(0.5), 0.5)
assert_fail(gr.test)
- func test_cast_int_math_eq_float():
+ func test_passes_when_cast_int_expression_to_float():
var i = 2
gr.test.assert_eq(5 / float(i), 2.5)
assert_pass(gr.test)
- func test_assert_eq_string_not_equal():
+ func test_fails_when_string_not_equal():
gr.test.assert_eq("one", "two", "Should Fail")
assert_fail(gr.test)
- func test_assert_eq_string_equal():
+ func test_passes_when_string_equal():
gr.test.assert_eq("one", "one", "Should Pass")
assert_pass(gr.test)
- func test_can_call_eq_without_text():
- gr.test.assert_eq(1, 1)
- assert_pass(gr.test)
class TestAssertNe:
extends BaseTestClass
- func test_can_call_ne_without_text():
+ func test_passes_with_integers_not_equal():
gr.test.assert_ne(1, 2)
assert_pass(gr.test)
- func test_assert_ne_number_not_equal():
- gr.test.assert_ne(1, 2)
- assert_pass(gr.test, 1, "Should pass, 1 != 2")
-
- func test_assert_ne_number_equal():
+ func test_fails_with_integers_equal():
gr.test.assert_ne(1, 1, "Should fail")
assert_fail(gr.test, 1, '1 = 1')
- func test_float_ne():
+ func test_passes_with_floats_not_equal():
gr.test.assert_ne(0.9, .009)
assert_pass(gr.test)
- func test_assert_ne_string_not_equal():
+ func test_passes_with_strings_not_equal():
gr.test.assert_ne("one", "two", "Should Pass")
assert_pass(gr.test)
- func test_assert_ne_string_equal():
+ func test_fails_with_strings_equal():
gr.test.assert_ne("one", "one", "Should Fail")
assert_fail(gr.test)
+class TestAssertAlmostEq:
+ extends BaseTestClass
+
+ func test_passes_with_integers_equal():
+ gr.test.assert_almost_eq(2, 2, 0, "Should pass, 2 == 2 +/- 0")
+ assert_pass(gr.test)
+
+ func test_passes_with_integers_almost_within_range():
+ gr.test.assert_almost_eq(1, 2, 1, "Should pass, 1 == 2 +/- 1")
+ gr.test.assert_almost_eq(3, 2, 1, "Should pass, 3 == 2 +/- 1")
+ assert_pass(gr.test, 2)
+
+ func test_fails_with_integers_outside_range():
+ gr.test.assert_almost_eq(0, 2, 1, "Should fail, 0 != 2 +/- 1")
+ gr.test.assert_almost_eq(4, 2, 1, "Should fail, 4 != 2 +/- 1")
+ assert_fail(gr.test, 2)
+
+ func test_passes_with_floats_within_range():
+ gr.test.assert_almost_eq(1.000, 1.000, 0.001, "Should pass, 1.000 == 1.000 +/- 0.001")
+ gr.test.assert_almost_eq(1.001, 1.000, 0.001, "Should pass, 1.001 == 1.000 +/- 0.001")
+ gr.test.assert_almost_eq(.999, 1.000, 0.001, "Should pass, .999 == 1.000 +/- 0.001")
+ assert_pass(gr.test, 3)
+
+ func test_fails_with_floats_outside_range():
+ gr.test.assert_almost_eq(2.002, 2.000, 0.001, "Should fail, 2.002 == 2.000 +/- 0.001")
+ gr.test.assert_almost_eq(1.998, 2.000, 0.001, "Should fail, 1.998 == 2.000 +/- 0.001")
+ assert_fail(gr.test, 2)
+
+ func test_passes_with_integers_within_float_range():
+ gr.test.assert_almost_eq(2, 1.9, .5, 'Should pass, 1.5 < 2 < 2.4')
+ assert_pass(gr.test)
+
+ func test_passes_with_float_within_integer_range():
+ gr.test.assert_almost_eq(2.5, 2, 1, 'Should pass, 1 < 2.5 < 3')
+ assert_pass(gr.test)
+
+ func test_passes_with_vector2s_eq():
+ gr.test.assert_almost_eq(Vector2(1.0, 1.0), Vector2(1.0, 1.0), Vector2(0.0, 0.0), "Should pass, Vector2(1.0, 1.0) == Vector2(1.0, 1.0) +/- Vector2(0.0, 0.0)")
+ assert_pass(gr.test)
+
+ func test_fails_with_vector2s_ne():
+ gr.test.assert_almost_eq(Vector2(1.0, 1.0), Vector2(2.0, 2.0), Vector2(0.0, 0.0), "Should fail, Vector2(1.0, 1.0) == Vector2(2.0, 2.0) +/- Vector2(0.0, 0.0)")
+ assert_fail(gr.test)
+
+ func test_passes_with_vector2s_almost_eq():
+ gr.test.assert_almost_eq(Vector2(1.0, 1.0), Vector2(2.0, 2.0), Vector2(1.0, 1.0), "Should pass, Vector2(1.0, 1.0) == Vector2(2.0, 2.0) +/- Vector2(1.0, 1.0)")
+ assert_pass(gr.test)
+
+class TestAssertAlmostNe:
+ extends BaseTestClass
+
+ func test_pass_with_integers_not_equal():
+ gr.test.assert_almost_ne(1, 2, 0, "Should pass, 1 != 2 +/- 0")
+ assert_pass(gr.test)
+
+ func test_fails_with_integers_equal():
+ gr.test.assert_almost_ne(2, 2, 0, "Should fail, 2 == 2 +/- 0")
+ assert_fail(gr.test)
+
+ func test_passes_with_integers_outside_range():
+ gr.test.assert_almost_ne(1, 3, 1, "Should pass, 1 != 3 +/- 1")
+ assert_pass(gr.test)
+
+ func test_fails_with_integers_within_range():
+ gr.test.assert_almost_ne(2, 3, 1, "Should fail, 2 == 3 +/- 1")
+ assert_fail(gr.test)
+
+ func test_passes_with_floats_outside_range():
+ gr.test.assert_almost_ne(1.000, 2.000, 0.001, "Should pass, 1.000 != 2.000 +/- 0.001")
+ assert_pass(gr.test)
+
+ func test_fails_with_floats_eq():
+ gr.test.assert_almost_ne(1.000, 1.000, 0.001, "Should fail, 1.000 == 1.000 +/- 0.001")
+ assert_fail(gr.test)
+
+ func test_fails_with_floats_within_range():
+ gr.test.assert_almost_ne(1.000, 2.000, 1.000, "Should fail, 1.000 == 2.000 +/- 1.000")
+ assert_fail(gr.test)
+
+ func test_passes_with_vector2s_outside_range():
+ gr.test.assert_almost_ne(Vector2(1.0, 1.0), Vector2(2.0, 2.0), Vector2(0.0, 0.0), "Should pass, Vector2(1.0, 1.0) != Vector2(2.0, 2.0) +/- Vector2(0.0, 0.0)")
+ assert_pass(gr.test)
+
+ func test_fails_with_vector2s_eq():
+ gr.test.assert_almost_ne(Vector2(1.0, 1.0), Vector2(1.0, 1.0), Vector2(0.0, 0.0), "Should fail, Vector2(1.0, 1.0) == Vector2(1.0, 1.0) +/- Vector2(0.0, 0.0)")
+ assert_fail(gr.test)
+
+ func test_passes_with_vector2s_almost_outside_range():
+ gr.test.assert_almost_ne(Vector2(1.0, 1.0), Vector2(2.0, 2.0), Vector2(0.9, 0.9), "Should pass, Vector2(1.0, 1.0) == Vector2(2.0, 2.0) +/- Vector2(0.9, 0.9)")
+ assert_pass(gr.test)
+
class TestAssertGt:
extends BaseTestClass
- func test_assert_gt_number_with_gt():
+ func test_passes_with_greater_integer():
gr.test.assert_gt(2, 1, "Should Pass")
assert_pass(gr.test, 1, '2 > 1')
- func test_assert_gt_number_with_lt():
+ func test_fails_with_less_than_integer():
gr.test.assert_gt(1, 2, "Should fail")
assert_fail(gr.test, 1, '1 < 2')
- func test_assert_gt_string_with_gt():
+ func test_passes_with_greater_string():
gr.test.assert_gt("b", "a", "Should Pass")
assert_pass(gr.test)
- func test_assert_gt_string_with_lt():
+ func test_fails_with_less_than_string():
gr.test.assert_gt("a", "b", "Sould Fail")
assert_fail(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestAssertLt:
extends BaseTestClass
@@ -186,6 +278,7 @@ class TestAssertLt:
gr.test.assert_lt("b", "a", "Should Fail")
assert_fail(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestAssertBetween:
extends BaseTestClass
@@ -237,6 +330,7 @@ class TestAssertBetween:
gr.test.assert_between('q', 'z', 'a', "Should fail")
assert_fail(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestAssertTrue:
extends BaseTestClass
@@ -252,7 +346,7 @@ class TestAssertTrue:
gr.test.assert_true(true)
assert_pass(gr.test)
-
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestAssertFalse:
extends BaseTestClass
@@ -268,6 +362,7 @@ class TestAssertFalse:
gr.test.assert_false(false, "Should pass")
assert_pass(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestAssertHas:
extends BaseTestClass
@@ -291,6 +386,7 @@ class TestAssertHas:
gr.test.assert_does_not_have(array, 20, 'Should not have it.')
assert_fail(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class testFailingDatatypeChecks:
extends BaseTestClass
@@ -320,6 +416,7 @@ class testFailingDatatypeChecks:
gr.test.assert_ne(null, Node2D.new())
assert_pass(gr.test, 2)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestPending:
extends BaseTestClass
@@ -456,6 +553,7 @@ class TestAssertExports:
gr.test.assert_exports(obj, "scene_property", TYPE_OBJECT)
assert_pass(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestAssertFileExists:
extends BaseTestClass
@@ -471,6 +569,7 @@ class TestAssertFileExists:
gr.test_with_gut.assert_file_exists(path)
assert_pass(gr.test_with_gut)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestAssertFileDne:
extends BaseTestClass
@@ -486,6 +585,7 @@ class TestAssertFileDne:
gr.test_with_gut.assert_file_does_not_exist(path)
assert_fail(gr.test_with_gut)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestAssertFileEmpty:
extends BaseTestClass
@@ -511,6 +611,7 @@ class TestAssertFileEmpty:
gr.test_with_gut.assert_file_empty(path)
assert_fail(gr.test_with_gut)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestAssertFileNotEmpty:
extends BaseTestClass
@@ -536,6 +637,7 @@ class TestAssertFileNotEmpty:
gr.test_with_gut.assert_file_not_empty(path)
assert_fail(gr.test_with_gut)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestSignalAsserts:
extends BaseTestClass
@@ -700,6 +802,7 @@ class TestSignalAsserts:
gr.test.assert_has_signal(gr.signal_object, SIGNALS.SCRIPT_SIGNAL)
assert_pass(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestExtendAsserts:
extends BaseTestClass
@@ -758,6 +861,7 @@ class TestExtendAsserts:
gr.test.assert_extends(a, HasSubclass2.SubClass)
assert_fail(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestStringContains:
extends BaseTestClass
@@ -785,6 +889,7 @@ class TestStringContains:
gr.test.assert_string_contains('This is a test.', 'this ', false)
assert_pass(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestStringStartsWith:
extends BaseTestClass
@@ -812,6 +917,7 @@ class TestStringStartsWith:
gr.test.assert_string_starts_with('This is a test.', 'tHI', false)
assert_pass(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestStringEndsWith:
extends BaseTestClass
@@ -839,6 +945,7 @@ class TestStringEndsWith:
gr.test.assert_string_ends_with('This is a test.', 'A teSt.', false)
assert_pass(gr.test)
+# TODO rename tests since they are now in an inner class. See NOTE at top about naming.
class TestAssertCalled:
extends BaseTestClass
@@ -875,7 +982,6 @@ class TestAssertCalled:
gr.test_with_gut.assert_called(doubled, 'has_two_params_one_default', [10, null])
assert_pass(gr.test_with_gut)
-
class TestAssertNotCalled:
extends BaseTestClass