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