From c5655514545c1b97798c6e173d8071e777dffaf7 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Fri, 2 Mar 2018 22:05:38 -0500 Subject: [PATCH 01/23] fix resize window bug --- CHANGES.md | 3 +++ addons/gut/gut_gui.gd | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index dec71800..3692e115 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +# 6.1.0 +* Fixed resize window handle bug. It was connecting to wrong signals and didn't work. + # 6.0.0 * Godot 3.0 compatibility * Combined GutTests repo so everything is now in one place. diff --git a/addons/gut/gut_gui.gd b/addons/gut/gut_gui.gd index 0ded9ef9..2f6eb99c 100644 --- a/addons/gut/gut_gui.gd +++ b/addons/gut/gut_gui.gd @@ -238,8 +238,8 @@ func setup_controls(): func set_it_up(): self.set_size(min_size) setup_controls() - self.connect("mouse_enter", self, "_on_mouse_enter") - self.connect("mouse_exit", self, "_on_mouse_exit") + self.connect("mouse_entered", self, "_on_mouse_enter") + self.connect("mouse_exited", self, "_on_mouse_exit") set_process(true) set_pause_mode(PAUSE_MODE_PROCESS) _update_controls() @@ -309,21 +309,21 @@ func _input(event): #if the mouse is somewhere within the debug window if(_mouse_in): #Check for mouse click inside the resize handle - if(event.type == InputEvent.MOUSE_BUTTON): + 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 - if(event.pos.x > get_size().x + get_position().x - 10 and event.pos.y > get_size().y + get_position().y - 10): + 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.pos + _mouse_down_pos = event.position else: _mouse_down = false #Reszie - if(event.type == InputEvent.MOUSE_MOTION): + if(event is InputEventMouseMotion): if(_mouse_down): if(get_size() >= min_size): - var new_size = get_size() + event.pos - _mouse_down_pos - var new_mouse_down_pos = event.pos + var new_size = get_size() + event.position - _mouse_down_pos + var new_mouse_down_pos = event.position if(new_size.x < min_size.x): new_size.x = min_size.x From ef469ba1575aab30adab6cfac362cc13e82395b1 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Fri, 2 Mar 2018 22:23:30 -0500 Subject: [PATCH 02/23] bump version, comments --- CHANGES.md | 1 + addons/gut/gut.gd | 2 +- addons/gut/gut_cmdln.gd | 2 +- addons/gut/plugin.cfg | 2 +- addons/gut/test.gd | 2 ++ 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3692e115..0b74b374 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). # 6.1.0 * Fixed resize window handle bug. It was connecting to wrong signals and didn't work. + # 6.0.0 * Godot 3.0 compatibility * Combined GutTests repo so everything is now in one place. diff --git a/addons/gut/gut.gd b/addons/gut/gut.gd index 9fb65e23..87af1a83 100644 --- a/addons/gut/gut.gd +++ b/addons/gut/gut.gd @@ -28,7 +28,7 @@ ################################################################################ # View readme for usage details. # -# Version 5.0.0 +# Version 6.1.0 ################################################################################ extends "res://addons/gut/gut_gui.gd" diff --git a/addons/gut/gut_cmdln.gd b/addons/gut/gut_cmdln.gd index 0e09ba77..3b28324a 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 5.0.0 +# Version 6.1.0 ################################################################################ extends SceneTree diff --git a/addons/gut/plugin.cfg b/addons/gut/plugin.cfg index 13bf7dde..7e7ea151 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.0.0" +version="6.1.0" script="gut_plugin.gd" diff --git a/addons/gut/test.gd b/addons/gut/test.gd index 8ec8c17b..87aa9ac0 100644 --- a/addons/gut/test.gd +++ b/addons/gut/test.gd @@ -47,6 +47,8 @@ var gut = null var passed = false var failed = false var _disable_strict_datatype_checks = false +# Holds all the text for a test's fail/pass. This is used for testing purposes +# to see the text of a failed sub-test in test_test.gd var _fail_pass_text = [] # Hash containing all the built in types in Godot. This provides an English From 823d3ab44ed5de885a39e4b4f454aba19bec9001 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Fri, 2 Mar 2018 23:26:03 -0500 Subject: [PATCH 03/23] summary working --- addons/gut/summary.gd | 108 ++++++++++++++++++++++++++++++++++++++ test/unit/test_summary.gd | 73 ++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 addons/gut/summary.gd create mode 100644 test/unit/test_summary.gd diff --git a/addons/gut/summary.gd b/addons/gut/summary.gd new file mode 100644 index 00000000..1809cf1b --- /dev/null +++ b/addons/gut/summary.gd @@ -0,0 +1,108 @@ +class Test: + var pass_texts = [] + var fail_texts = [] + var pending_texts = [] + + func to_s(): + var pad = ' ' + var to_return = '' + for i in range(fail_texts.size()): + to_return += str(pad, 'FAIL: ', fail_texts[i], "\n") + for i in range(pending_texts.size()): + to_return += str(pad, 'Pending: ', pending_texts[i], "\n") + return to_return + +class Script: + var name = 'NOT_SET' + var _tests = {} + var _test_order = [] + + func _init(script_name): + name = script_name + + func get_pass_count(): + var count = 0 + for key in _tests: + count += _tests[key].pass_texts.size() + return count + + func get_fail_count(): + var count = 0 + for key in _tests: + count += _tests[key].fail_texts.size() + return count + + func get_pending_count(): + var count = 0 + for key in _tests: + count += _tests[key].pending_texts.size() + return count + + func get_test_obj(name): + if(!_tests.has(name)): + _tests[name] = Test.new() + _test_order.append(name) + return _tests[name] + + func add_pass(test_name, reason): + var t = get_test_obj(test_name) + t.pass_texts.append(reason) + + func add_fail(test_name, reason): + var t = get_test_obj(test_name) + t.fail_texts.append(reason) + + func add_pending(test_name, reason): + var t = get_test_obj(test_name) + t.pending_texts.append(reason) + +var _scripts = [] + +func add_script(name): + _scripts.append(Script.new(name)) + +func get_scripts(): + return _scripts + +func get_current_script(): + return _scripts[_scripts.size() - 1] + +func add_pass(test_name, reason = ''): + get_current_script().add_pass(test_name, reason) + +func add_fail(test_name, reason = ''): + get_current_script().add_fail(test_name, reason) + +func add_pending(test_name, reason = ''): + get_current_script().add_pending(test_name, reason) + +func get_test_text(test_name): + return test_name + "\n" + get_current_script().get_test_obj(test_name).to_s() + +func get_summary_text(): + var totals = { + pas = 0, + pend = 0, + fail = 0, + tests = 0 + } + var to_return = '' + for s in range(_scripts.size()): + totals.pas += _scripts[s].get_pass_count() + totals.pend += _scripts[s].get_pending_count() + totals.fail += _scripts[s].get_fail_count() + totals.tests += _scripts[s]._test_order.size() + + if(_scripts[s].get_fail_count() > 0 or _scripts[s].get_pending_count() > 0): + to_return += _scripts[s].name + "\n" + for t in range(_scripts[s]._test_order.size()): + var tname = _scripts[s]._test_order[t] + var test = _scripts[s].get_test_obj(tname) + if(test.fail_texts.size() > 0 or test.pending_texts.size() > 0): + to_return += str(' -', tname, "\n", test.to_s()) + var header = str(_scripts.size(), " scripts.\n") + header += str(totals.tests, " tests\n") + header += str(totals.pas, " passing asserts\n") + header += str(totals.pend, " pending\n") + header += str(totals.fail, " failing asserts\n") + return to_return + header diff --git a/test/unit/test_summary.gd b/test/unit/test_summary.gd new file mode 100644 index 00000000..be09f6e2 --- /dev/null +++ b/test/unit/test_summary.gd @@ -0,0 +1,73 @@ +extends "res://addons/gut/test.gd" + +var Summary = load('res://addons/gut/summary.gd') + +var gr = { + summary = null +} + + +func setup(): + gr.summary = Summary.new() + +func test_can_add_script(): + gr.summary.add_script('script1') + +func test_can_get_scripts(): + gr.summary.add_script('script1') + gr.summary.add_script('script2') + assert_eq(gr.summary.get_scripts().size(), 2) + +func test_get_current_script_returns_the_most_recent_script(): + gr.summary.add_script('script1') + gr.summary.add_script('script2') + assert_eq(gr.summary.get_current_script().name, 'script2') + +func test_adding_a_new_script_changes_current(): + gr.summary.add_script('script1') + gr.summary.add_script('script2') + gr.summary.add_script('script3') + assert_eq(gr.summary.get_current_script().name, 'script3') + +func test_can_add_pass(): + gr.summary.add_script('script1') + gr.summary.add_pass('test_name') + assert_eq(gr.summary.get_current_script().get_pass_count(), 1) + +func test_can_add_fail(): + gr.summary.add_script('script1') + gr.summary.add_fail('test_name', 'reason') + assert_eq(gr.summary.get_current_script().get_fail_count(), 1) + +func test_can_get_failure_reason(): + gr.summary.add_script('script1') + gr.summary.add_fail('test_name', 'reason') + assert_ne(gr.summary.get_test_text('test_name').find('reason'), -1) + +func test_can_add_pending(): + gr.summary.add_script('script66') + gr.summary.add_pending('test_name', 'reason') + assert_eq(gr.summary.get_current_script().get_pending_count(), 1) + assert_ne(gr.summary.get_test_text('test_name').find('reason'), -1) + +func test_get_test_text_returns_test_name(): + gr.summary.add_script('script1') + gr.summary.add_pass('test_name', 'reason') + assert_ne(gr.summary.get_test_text('test_name').find('test_name'), -1) + +func test_check_out_this_summary(): + gr.summary.add_script('script_all_pass') + gr.summary.add_pass('test_pass1') + gr.summary.add_pass('test_pass2') + + gr.summary.add_script('script_with_pending') + gr.summary.add_pass('test_pass1') + gr.summary.add_pending('test_pending', 'b/c I said so') + + gr.summary.add_script('script_with_failure') + gr.summary.add_fail('test_fail', 'it is wrong') + + gr.summary.add_script('script_complex') + gr.summary.add_fail('pending_fail', 'fail') + gr.summary.add_pending('pending_fail', 'pending') + print(gr.summary.get_summary_text()) From c55a4547f589f0176c8ad5ddeca0f17e773df2b8 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Sat, 3 Mar 2018 00:45:24 -0500 Subject: [PATCH 04/23] better summary implemented --- addons/gut/gut.gd | 87 ++++++++++------------------ addons/gut/gut_gui.gd | 8 +-- addons/gut/summary.gd | 62 ++++++++++++++------ addons/gut/test.gd | 5 +- scenes/main.tscn | 2 +- test/samples/test_readme_examples.gd | 8 +-- test/unit/test_summary.gd | 1 + 7 files changed, 85 insertions(+), 88 deletions(-) diff --git a/addons/gut/gut.gd b/addons/gut/gut.gd index 87af1a83..20212f96 100644 --- a/addons/gut/gut.gd +++ b/addons/gut/gut.gd @@ -111,19 +111,13 @@ var _runtime_timer = Timer.new() const RUNTIME_START_TIME = float(20000.0) var _unit_test_name = '' +var Summary = load('res://addons/gut/summary.gd') +var _new_summary = null const SIGNAL_TESTS_FINISHED = 'tests_finished' const SIGNAL_STOP_YIELD_BEFORE_TEARDOWN = 'stop_yeild_before_teardown' -# Add test summaries to the local summary. -func _add_summaries(test): - _summary.asserts += test.get_summary().asserts - _summary.passed += test.get_summary().passed - _summary.failed += test.get_summary().failed - _summary.tests += test.get_summary().tests - _summary.pending += test.get_summary().pending - # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ func _init(): @@ -310,31 +304,25 @@ func _parse_tests(script): # ------------------------------------------------------------------------------ func _get_summary_text(): var to_return = "*****************\nRun Summary\n*****************\n" - to_return += str(' scripts: ', _summary.scripts, "\n") - to_return += str(' tests: ', _summary.tests, "\n") - to_return += str(' asserts: ', _summary.asserts, "\n") - to_return += str(' passed: ', _summary.passed, "\n") - to_return += str(' pending: ', _summary.pending, "\n") - if(_summary.moved_methods > 0): - to_return += str(' moved: ', _summary.moved_methods, "\n") - to_return += str(' failed: ', _summary.failed, "\n") - to_return += "\n\n" + to_return += "\n" + _new_summary.get_summary_text() + "\n" - if(_summary.tests > 0): - to_return += '+++ ' + str(_summary.passed) + ' passed ' + str(_summary.failed) + ' failed. ' + \ + if(_new_summary.get_totals().tests > 0): + to_return += '+++ ' + str(_new_summary.get_totals().passing) + ' passed ' + str(_new_summary.get_totals().failing) + ' failed. ' + \ "Tests finished in: " + _ctrls.runtime_label.get_text() + ' +++' var c = Color(0, 1, 0) - if(_summary.passed != _summary.asserts): + if(_new_summary.get_totals().failing > 0): c = Color(1, 0, 0) + elif(_new_summary.get_totals().pending > 0): + c = Color(1, 1, .8) + _ctrls.text_box.add_color_region('+++', '+++', c) else: to_return += '+++ No tests ran +++' _ctrls.text_box.add_color_region('+++', '+++', Color(1, 0, 0)) if(_summary.moved_methods > 0): - to_return += "\n" + """ -Moved Methods + to_return += "\nMoved Methods (" + str(_summary.moved_methods) + ')' + """ ------------- It looks like you have some methods that have been moved. These methods were moved from the gut object to the test object so that you don't have to prefix @@ -342,19 +330,14 @@ them with 'gut.'. This means less typing for you and better organization of the code. I'm sorry for the inconvenience but I promise this will make things easier in the future...I'm pretty sure at least. Thanks for using Gut!""" - return to_return + return to_return + "\n\n" # ------------------------------------------------------------------------------ # Initialize variables for each run of a single test script. # ------------------------------------------------------------------------------ func _init_run(): _test_script_objects = [] - _summary.asserts = 0 - _summary.passed = 0 - _summary.failed = 0 - _summary.tests = 0 - _summary.scripts = 0 - _summary.pending = 0 + _new_summary = Summary.new() _summary.tally_passed = 0 _summary.tally_failed = 0 @@ -380,23 +363,6 @@ func _init_run(): func _end_run(): var failed_tests = [] var more_than_one = _test_script_objects.size() > 1 - # no need to summarize the run if only one script was run - if(more_than_one): - p("----\nAll Passing/Pending\n----") - - for i in range(_test_script_objects.size()): - if(more_than_one): - if(_test_script_objects[i].get_fail_count() == 0): - p(_test_script_objects[i].get_summary_text()) - else: - failed_tests.append(_test_script_objects[i]) - _add_summaries(_test_script_objects[i]) - - if(more_than_one): - p("----\nWith Failures\n----") - - for i in range(failed_tests.size()): - p(failed_tests[i].get_summary_text()) p(_get_summary_text(), 0) @@ -473,6 +439,7 @@ func _test_the_scripts(): _tests.clear() set_title('Running: ' + _test_scripts[s]) _print_script_heading(_test_scripts[s]) + _new_summary.add_script(_test_scripts[s]) if(!file.file_exists(_test_scripts[s])): p("FAILED COULD NOT FIND FILE: " + _test_scripts[s]) @@ -480,7 +447,6 @@ func _test_the_scripts(): var test_script = _init_test_script(_test_scripts[s]) _test_script_objects.append(test_script) var script_result = null - _summary.scripts += 1 test_script.prerun_setup() @@ -493,6 +459,8 @@ func _test_the_scripts(): _ctrls.test_progress.set_max(_tests.size()) for i in range(_tests.size()): _current_test = _tests[i] + _new_summary.add_test(_current_test.name) + if((_unit_test_name != '' and _current_test.name.find(_unit_test_name) > -1) or (_unit_test_name == '')): p(_current_test.name, 1) @@ -504,7 +472,6 @@ func _test_the_scripts(): yield(_yield_between.timer, 'timeout') test_script.setup() - _summary.tests += 1 script_result = test_script.call(_current_test.name) #When the script yields it will return a GDFunctionState object @@ -560,17 +527,24 @@ func _test_the_scripts(): -func _pass(): +func _pass(text=''): _summary.tally_passed += 1 _update_controls() + if(_current_test): + _new_summary.add_pass(_current_test.name, text) -func _fail(): +func _fail(text=''): _summary.tally_failed += 1 if(_current_test != null): + var line_text = ' at line ' + str(_current_test.line_number) + _new_summary.add_fail(_current_test.name, text + "\n " + line_text) _current_test.passed = false - p(' at line ' + str(_current_test.line_number), LOG_LEVEL_FAIL_ONLY) + p(line_text, LOG_LEVEL_FAIL_ONLY) _update_controls() +func _pending(text=''): + if(_current_test): + _new_summary.add_pending(_current_test.name, text) ######################### # # public @@ -744,31 +718,32 @@ func clear_text(): # Get the number of tests that were ran # ------------------------------------------------------------------------------ func get_test_count(): - return _summary.tests + return _new_summary.get_totals().tests # ------------------------------------------------------------------------------ # Get the number of assertions that were made # ------------------------------------------------------------------------------ func get_assert_count(): - return _summary.asserts + var t = _new_summary.get_totals() + return t.passing + t.failing # ------------------------------------------------------------------------------ # Get the number of assertions that passed # ------------------------------------------------------------------------------ func get_pass_count(): - return _summary.passed + return _new_summary.get_totals().passing # ------------------------------------------------------------------------------ # Get the number of assertions that failed # ------------------------------------------------------------------------------ func get_fail_count(): - return _summary.failed + return _new_summary.get_totals().failing # ------------------------------------------------------------------------------ # Get the number of tests flagged as pending # ------------------------------------------------------------------------------ func get_pending_count(): - return _summary.pending + return _new_summary.get_totals().pending # ------------------------------------------------------------------------------ # Set whether it should print to console or not. Default is yes. diff --git a/addons/gut/gut_gui.gd b/addons/gut/gut_gui.gd index 2f6eb99c..b61e8972 100644 --- a/addons/gut/gut_gui.gd +++ b/addons/gut/gut_gui.gd @@ -30,14 +30,8 @@ ################################################################################ extends WindowDialog -# various counters +# various counters. Most have been moved to the Summary object but not all. var _summary = { - asserts = 0, - passed = 0, - failed = 0, - tests = 0, - scripts = 0, - pending = 0, moved_methods = 0, # these are used to display the tally in the top right corner. Since the # implementation changed to summing things up at the end, the running diff --git a/addons/gut/summary.gd b/addons/gut/summary.gd index 1809cf1b..4a9f91d7 100644 --- a/addons/gut/summary.gd +++ b/addons/gut/summary.gd @@ -4,10 +4,10 @@ class Test: var pending_texts = [] func to_s(): - var pad = ' ' + var pad = ' ' var to_return = '' for i in range(fail_texts.size()): - to_return += str(pad, 'FAIL: ', fail_texts[i], "\n") + to_return += str(pad, 'FAILED: ', fail_texts[i], "\n") for i in range(pending_texts.size()): to_return += str(pad, 'Pending: ', pending_texts[i], "\n") return to_return @@ -56,6 +56,8 @@ class Script: var t = get_test_obj(test_name) t.pending_texts.append(reason) + + var _scripts = [] func add_script(name): @@ -67,6 +69,9 @@ func get_scripts(): func get_current_script(): return _scripts[_scripts.size() - 1] +func add_test(test_name): + get_current_script().get_test_obj(test_name) + func add_pass(test_name, reason = ''): get_current_script().add_pass(test_name, reason) @@ -79,30 +84,51 @@ func add_pending(test_name, reason = ''): func get_test_text(test_name): return test_name + "\n" + get_current_script().get_test_obj(test_name).to_s() -func get_summary_text(): +func get_totals(): var totals = { - pas = 0, - pend = 0, - fail = 0, - tests = 0 + passing = 0, + pending = 0, + failing = 0, + tests = 0, + scripts = 0 } - var to_return = '' + for s in range(_scripts.size()): - totals.pas += _scripts[s].get_pass_count() - totals.pend += _scripts[s].get_pending_count() - totals.fail += _scripts[s].get_fail_count() + totals.passing += _scripts[s].get_pass_count() + totals.pending += _scripts[s].get_pending_count() + totals.failing += _scripts[s].get_fail_count() totals.tests += _scripts[s]._test_order.size() + totals.scripts = _scripts.size() + + return totals + +func get_summary_text(): + var _totals = get_totals() + var to_return = '' + for s in range(_scripts.size()): if(_scripts[s].get_fail_count() > 0 or _scripts[s].get_pending_count() > 0): to_return += _scripts[s].name + "\n" for t in range(_scripts[s]._test_order.size()): var tname = _scripts[s]._test_order[t] var test = _scripts[s].get_test_obj(tname) if(test.fail_texts.size() > 0 or test.pending_texts.size() > 0): - to_return += str(' -', tname, "\n", test.to_s()) - var header = str(_scripts.size(), " scripts.\n") - header += str(totals.tests, " tests\n") - header += str(totals.pas, " passing asserts\n") - header += str(totals.pend, " pending\n") - header += str(totals.fail, " failing asserts\n") - return to_return + header + to_return += str(' - ', tname, "\n", test.to_s()) + + var header = "*** Totals ***\n" + header += str(' scripts: ', _scripts.size(), "\n") + header += str(' tests: ', _totals.tests, "\n") + header += str(' passing asserts: ', _totals.passing, "\n") + header += str(' failing asserts: ',_totals.failing, "\n") + header += str(' pending: ', _totals.pending, "\n") + + return to_return + "\n" + header + + + + + # to_return += str(' scripts: ', _summary.scripts, "\n") + # to_return += str(' tests: ', _summary.tests, "\n") + # to_return += str(' asserts: ', _summary.asserts, "\n") + # to_return += str(' passed: ', _summary.passed, "\n") + # to_return += str(' pending: ', _summary.pending, "\n") diff --git a/addons/gut/test.gd b/addons/gut/test.gd index 87aa9ac0..4ea1016b 100644 --- a/addons/gut/test.gd +++ b/addons/gut/test.gd @@ -132,7 +132,7 @@ func _fail(text): _fail_pass_text.append(msg) if(gut): gut.p(msg, gut.LOG_LEVEL_FAIL_ONLY) - gut._fail() + gut._fail(text) gut.end_yielded_test() # ------------------------------------------------------------------------------ @@ -145,7 +145,7 @@ func _pass(text): _fail_pass_text.append(msg) if(gut): gut.p(msg, gut.LOG_LEVEL_ALL_ASSERTS) - gut._pass() + gut._pass(text) gut.end_yielded_test() # Checks if the datatypes passed in match. If they do not then this will cause @@ -490,6 +490,7 @@ func pending(text=""): gut.p("Pending") else: gut.p("Pending: " + text) + gut._pending(text) gut.end_yielded_test() # ------------------------------------------------------------------------------ diff --git a/scenes/main.tscn b/scenes/main.tscn index 5b03da5b..e8dbbe4a 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -44,7 +44,7 @@ _file_prefix = "test_" _file_extension = ".gd" _directory1 = "res://test/unit" _directory2 = "res://test/integration" -_directory3 = "" +_directory3 = "res://test/samples" _directory4 = "" _directory5 = "" _directory6 = "" diff --git a/test/samples/test_readme_examples.gd b/test/samples/test_readme_examples.gd index 33678016..fe3c9d87 100644 --- a/test/samples/test_readme_examples.gd +++ b/test/samples/test_readme_examples.gd @@ -199,17 +199,17 @@ class MovingNode: set_process(true) func _process(delta): - set_pos(get_pos() + Vector2(_speed * delta, 0)) + set_position(get_position() + Vector2(_speed * delta, 0)) func test_illustrate_yield(): var moving_node = MovingNode.new() add_child(moving_node) - moving_node.set_pos(Vector2(0, 0)) + moving_node.set_position(Vector2(0, 0)) # While the yield happens, the node should move yield(yield_for(2), YIELD) - assert_gt(moving_node.get_pos().x, 0) - assert_between(moving_node.get_pos().x, 3.9, 4, 'it should move almost 4 whatevers at speed 2') + assert_gt(moving_node.get_position().x, 0) + assert_between(moving_node.get_position().x, 3.9, 4, 'it should move almost 4 whatevers at speed 2') func test_illustrate_end_test(): yield(yield_for(1), YIELD) diff --git a/test/unit/test_summary.gd b/test/unit/test_summary.gd index be09f6e2..aea73975 100644 --- a/test/unit/test_summary.gd +++ b/test/unit/test_summary.gd @@ -71,3 +71,4 @@ func test_check_out_this_summary(): gr.summary.add_fail('pending_fail', 'fail') gr.summary.add_pending('pending_fail', 'pending') print(gr.summary.get_summary_text()) + assert_true(true) From 14d1911e2503647e18722589cc367d9b9b8c63a0 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Sat, 3 Mar 2018 00:47:52 -0500 Subject: [PATCH 05/23] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 0b74b374..0116fc35 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). # 6.1.0 * Fixed resize window handle bug. It was connecting to wrong signals and didn't work. +* Improved summary. It now lists all failures and pendings instead of just listing the scripts that have failures or pending tests. # 6.0.0 From 7280a7eaa80ac45a3cf192166d5d8b169864f8c8 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Sat, 3 Mar 2018 02:18:02 -0500 Subject: [PATCH 06/23] bug with summary adding tests not matching string --- addons/gut/gut.gd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/gut/gut.gd b/addons/gut/gut.gd index 20212f96..87f45593 100644 --- a/addons/gut/gut.gd +++ b/addons/gut/gut.gd @@ -459,12 +459,11 @@ func _test_the_scripts(): _ctrls.test_progress.set_max(_tests.size()) for i in range(_tests.size()): _current_test = _tests[i] - _new_summary.add_test(_current_test.name) if((_unit_test_name != '' and _current_test.name.find(_unit_test_name) > -1) or (_unit_test_name == '')): p(_current_test.name, 1) - + _new_summary.add_test(_current_test.name) #yield so things paint if(_should_yield_now()): _yield_between.timer.set_wait_time(0.001) From 9b6ef156475f32f0f4b6041f5b88e97d7bc5cd7e Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Sat, 3 Mar 2018 02:22:41 -0500 Subject: [PATCH 07/23] some comments --- addons/gut/test.gd | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/addons/gut/test.gd b/addons/gut/test.gd index 4ea1016b..2d3b612f 100644 --- a/addons/gut/test.gd +++ b/addons/gut/test.gd @@ -148,9 +148,11 @@ func _pass(text): gut._pass(text) gut.end_yielded_test() +# ------------------------------------------------------------------------------ # Checks if the datatypes passed in match. If they do not then this will cause # a fail to occur. If they match then TRUE is returned, FALSE if not. This is # used in all the assertions that compare values. +# ------------------------------------------------------------------------------ func _do_datatypes_match__fail_if_not(got, expected, text): var passed = true @@ -300,6 +302,7 @@ func assert_file_empty(file_path): _fail(disp) # ------------------------------------------------------------------------------ +# Asserts the specified file is not empty # ------------------------------------------------------------------------------ func assert_file_not_empty(file_path): var disp = 'expected [' + file_path + '] to contain data' @@ -533,9 +536,4 @@ func get_summary_text(): to_return += str("\n ", _summary.pending, ' pending') if(_summary.failed > 0): to_return += str("\n ", _summary.failed, ' failed.') - # to_return += str(' tests: ', _summary.tests, "\n") - # to_return += str(' asserts: ', _summary.asserts, "\n") - # to_return += str(' passed: ', _summary.passed, "\n") - # to_return += str(' pending: ', _summary.pending, "\n") - # to_return += str(' failed: ', _summary.failed) return to_return From fba851fa54ba33136cf91a417c263e16d4929c98 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Sat, 3 Mar 2018 15:53:51 -0500 Subject: [PATCH 08/23] yield to signal or max time working --- addons/gut/gut.gd | 51 +++++++++++++++++----- addons/gut/signal_watcher.gd | 4 ++ addons/gut/test.gd | 8 +++- test/unit/test_gut_yielding.gd | 74 +++++++++++++++++++++++++++++++- test/unit/test_signal_watcher.gd | 6 +++ 5 files changed, 131 insertions(+), 12 deletions(-) diff --git a/addons/gut/gut.gd b/addons/gut/gut.gd index 87f45593..64de3436 100644 --- a/addons/gut/gut.gd +++ b/addons/gut/gut.gd @@ -91,7 +91,7 @@ var _current_test = null var _log_text = "" var _pause_before_teardown = false -# when true _pase_before_teardown will be ignored. useful +# when true _pause_before_teardown will be ignored. useful # when batch processing and you don't want to watch. var _ignore_pause_before_teardown = false var _wait_timer = Timer.new() @@ -103,7 +103,7 @@ var _yield_between = { tests_since_last_yield = 0 } -var _set_yield_time_called = false +var _was_yield_method_called = false # used when yielding to gut instead of some other # signal. Start with set_yield_time() var _yield_timer = Timer.new() @@ -114,6 +114,10 @@ var _unit_test_name = '' var Summary = load('res://addons/gut/summary.gd') var _new_summary = null +var _yielding_to = { + obj = null, + signal_name = '' +} const SIGNAL_TESTS_FINISHED = 'tests_finished' const SIGNAL_STOP_YIELD_BEFORE_TEARDOWN = 'stop_yeild_before_teardown' @@ -159,7 +163,7 @@ func _ready(): add_child(_yield_timer) _yield_timer.set_one_shot(true) - _yield_timer.connect('timeout', self, '_on_yield_timer_timeout') + _yield_timer.connect('timeout', self, '_yielding_callback') # This timer is started, but it should never finish. Used # to determine how long it took to run the tests since @@ -201,8 +205,24 @@ func _process(delta): # Timeout for the built in timer. emits the timeout signal. Start timer # with set_yield_time() # ------------------------------------------------------------------------------ -func _on_yield_timer_timeout(): - emit_signal('timeout') +func _yielding_callback(from_obj=false): + if(_yielding_to.obj): + _yielding_to.obj.disconnect(_yielding_to.signal_name, self, '_yielding_callback') + _yielding_to.obj = null + _yielding_to.signal_name = '' + + if(from_obj): + # we must yiled for a little longer after the signal is emitted so that + # the signal can propigate to other objects. This was discovered trying + # to assert that obj/signal_name was emitted. Without this extra delay + # the yield returns and processing finishes before the rest of the + # objects can get the signal. This works b/c the timer will timeout + # and come back into this method but the obj and signal_name will be + # null + _yield_timer.set_wait_time(.1) + _yield_timer.start() + else: + emit_signal('timeout') # ------------------------------------------------------------------------------ # Run either the selected test or all tests. @@ -355,8 +375,6 @@ func _init_run(): _ctrls.script_progress.set_max(_test_scripts.size()) _ctrls.script_progress.set_value(0) - - # ------------------------------------------------------------------------------ # Print out run information and close out the run. # ------------------------------------------------------------------------------ @@ -475,9 +493,9 @@ func _test_the_scripts(): script_result = test_script.call(_current_test.name) #When the script yields it will return a GDFunctionState object if(_is_function_state(script_result)): - if(!_set_yield_time_called): + if(!_was_yield_method_called): p('/# Yield detected, waiting #/') - _set_yield_time_called = false + _was_yield_method_called = false _waiting = true while(_waiting): p(WAITING_MESSAGE, 2) @@ -840,7 +858,20 @@ func set_yield_time(time, text=''): else: msg += ': ' + text + ' #/' p(msg, 1) - _set_yield_time_called = true + _was_yield_method_called = true + return self + +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +func set_yield_signal_or_time(obj, signal_name, max_wait, text=''): + obj.connect(signal_name, self, '_yielding_callback', [true]) + _yielding_to.obj = obj + _yielding_to.signal_name = signal_name + + _yield_timer.set_wait_time(max_wait) + _yield_timer.start() + _was_yield_method_called = true + p(str('/# Yielding to signal "', signal_name, '" or for ', max_wait, ' seconds #/')) return self # ------------------------------------------------------------------------------ diff --git a/addons/gut/signal_watcher.gd b/addons/gut/signal_watcher.gd index da013710..943fd8f2 100644 --- a/addons/gut/signal_watcher.gd +++ b/addons/gut/signal_watcher.gd @@ -54,6 +54,10 @@ const ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_' var _watched_signals = {} func _add_watched_signal(obj, name): + # SHORTCIRCUIT + if(_watched_signals.has(obj) and _watched_signals[obj].has(name)): + return + if(!_watched_signals.has(obj)): _watched_signals[obj] = {name:[]} else: diff --git a/addons/gut/test.gd b/addons/gut/test.gd index 2d3b612f..02df6be2 100644 --- a/addons/gut/test.gd +++ b/addons/gut/test.gd @@ -375,7 +375,7 @@ func watch_signals(object): # the object does not have the specified signal # ------------------------------------------------------------------------------ func assert_signal_emitted(object, signal_name, text=""): - var disp = str('Expected object ', object, ' to emit signal [', signal_name, ']: ', text) + var disp = str('Expected object ', object, ' to have emitted signal [', signal_name, ']: ', text) if(_can_make_signal_assertions(object, signal_name)): if(_signal_watcher.did_emit(object, signal_name)): _pass(disp) @@ -505,6 +505,12 @@ func pending(text=""): func yield_for(time, msg=''): return gut.set_yield_time(time, msg) +func yield_to(obj, signal_name, max_wait, msg=''): + watch_signals(obj) + gut.set_yield_signal_or_time(obj, signal_name, max_wait, msg) + + return gut + func end_test(): gut.end_yielded_test() diff --git a/test/unit/test_gut_yielding.gd b/test/unit/test_gut_yielding.gd index 99c1ee91..1f5823f8 100644 --- a/test/unit/test_gut_yielding.gd +++ b/test/unit/test_gut_yielding.gd @@ -1,11 +1,29 @@ #------------------------------------------------------------------------------- # All of these tests require some amount of user interaction or verifying of the -# output. +# output. #------------------------------------------------------------------------------- extends "res://addons/gut/test.gd" var timer = Timer.new() +class TimedSignaler: + extends Node2D + + signal the_signal + var _timer = Timer.new() + + func _ready(): + add_child(_timer) + _timer.connect('timeout', self, '_on_timer_timeout') + _timer.one_shot = true + + func _on_timer_timeout(): + emit_signal('the_signal') + + func emit_after(time): + _timer.set_wait_time(time) + _timer.start() + func prerun_setup(): add_child(timer) timer.set_wait_time(1) @@ -86,3 +104,57 @@ func test_failing_assert_ends_yield(): func test_pending_ends_yield(): yield(yield_for(0.5), YIELD) pending('this is pending but should end test') + +func test_can_yield_to_signal(): + var signaler = TimedSignaler.new() + add_child(signaler) + signaler.emit_after(.5) + yield(yield_to(signaler, 'the_signal', 10), YIELD) + assert_true(true, 'we got here') + +func test_after_yield_to_gut_disconnects_from_signal(): + var signaler = TimedSignaler.new() + add_child(signaler) + signaler.emit_after(.5) + yield(yield_to(signaler, 'the_signal', 1), YIELD) + assert_false(signaler.is_connected('the_signal', gut, '_yielding_callback')) + remove_child(signaler) + +func test_yield_to__will_disconnect_after_yield_finishes_and_signal_wasnt_emitted(): + var signaler = TimedSignaler.new() + add_child(signaler) + yield(yield_to(signaler, 'the_signal', 1), YIELD) + assert_false(signaler.is_connected('the_signal', gut, '_yielding_callback')) + remove_child(signaler) + +func test_yield_to__will_wait_max_time(): + var signaler = TimedSignaler.new() + add_child(signaler) + yield(yield_to(signaler, 'the_signal', 2), YIELD) + assert_true(true, 'we got here') + remove_child(signaler) + +func test_yield_to__will_stop_timer_when_signal_emitted(): + var signaler = TimedSignaler.new() + add_child(signaler) + signaler.emit_after(.5) + yield(yield_to(signaler, 'the_signal', 2), YIELD) + assert_eq(gut._yield_timer.time_left, 0.0) + remove_child(signaler) + +func test_yield_to__watches_signals(): + var signaler = TimedSignaler.new() + add_child(signaler) + watch_signals(signaler) + signaler.emit_after(.5) + yield(yield_to(signaler, 'the_signal', 5), YIELD) + assert_signal_emitted(signaler, 'the_signal') + remove_child(signaler) + +func test_what_is_wrong(): + var signaler = TimedSignaler.new() + add_child(signaler) + watch_signals(signaler) + signaler.emit_after(0.5) + yield(yield_for(1), YIELD) + assert_signal_emitted(signaler, 'the_signal') diff --git a/test/unit/test_signal_watcher.gd b/test/unit/test_signal_watcher.gd index 5f5dd765..df36b57a 100644 --- a/test/unit/test_signal_watcher.gd +++ b/test/unit/test_signal_watcher.gd @@ -216,6 +216,12 @@ func test_watch_signals_watches_all_signals_on_an_object(): for sig in SIGNALS: assert_true(gr.sw.is_watching(gr.so, SIGNALS[sig]), str('it should be watching: ', SIGNALS[sig])) +func test_watch_signals_ignores_duplicates(): + gr.sw.watch_signals(gr.so) + gr.sw.watch_signals(gr.so) + gut.p("-- LOOK FOR RED HERE --") + assert_true(true) + # #################### # Clear # #################### From 54b3b5b2e5851242692410a67b15eb7015f4e1a1 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Sat, 3 Mar 2018 16:27:14 -0500 Subject: [PATCH 09/23] change log and readme --- CHANGES.md | 10 +++++++++- README.md | 17 ++++++++++++++++- addons/gut/gut.gd | 3 +-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0116fc35..0fd9b6ef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,15 @@ This project adheres to [Semantic Versioning](http://semver.org/). # 6.1.0 * Fixed resize window handle bug. It was connecting to wrong signals and didn't work. * Improved summary. It now lists all failures and pendings instead of just listing the scripts that have failures or pending tests. - +* Added `yield_to` which allows you to `yield` to a signal or a maximum amount of time. This keeps your tests moving along if you yield to a signal that never gets emitted. Now the test will fail after an amount of time instead of sitting in limbo forever. This will also watch the signals on the object so you can make asserts about signals after the `yield` and you can save a line of code. +Example: +``` python +# wait for my_object to emit the signal 'my_signal' +# or 5 seconds, whichever comes first. +yield(yield_to(my_object, 'my_signal', 5), YIELD) +assert_signal_emitted(my_object, 'my_signal', \ + 'Maybe it did, maybe it didnt, but we still got here.') +``` # 6.0.0 * Godot 3.0 compatibility diff --git a/README.md b/README.md index b249cfcd..edd40094 100644 --- a/README.md +++ b/README.md @@ -778,8 +778,19 @@ func test_yield_to_custom_signal(): yield(my_object, 'custom_signal') assert_true(some_condition, 'After signal fired, this should be true') ``` +### yield_to +Sometimes you need to wait for a signal to be emitted, but you can never really be sure it will, we are making tests after all. You could `yield` to that signal in your test and hope it gets emitted. If it doesn't though, your test will just hang forever. The `yield_to` method addresses this by allowing you to `yield` to a signal or a maximum amount of time, whichever occurs first. You must make sure the 2nd parameter to `yield` is the `YIELD` constant. This constant is available to all test scripts. As an extra bonus, Gut will watch the signals on the object you passed in, so you can save yourself a call to `watch_signals` if you want, but you don't have to. How all this magic works is covered a couple of sections down. -Another use case I have come across is when creating integration tests and you want to verify that a complex interaction ends with an expected result. In this case you might have an idea of how long the interaction will take to play out but you don't have a signal that you can attach to. Instead you want to pause your test execution until that time has elapsed. For this, Gut has the `yield_for` method. This method has a little magic in it, but all you really need to know is that the following line will pause your test execution for 5 seconds while the rest of your code executes as expected: `yield(yield_for(5), YIELD)`. +``` python +# wait for my_object to emit the signal 'my_signal' +# or 5 seconds, whichever comes first. +yield(yield_to(my_object, 'my_signal', 5), YIELD) +assert_signal_emitted(my_object, 'my_signal', \ + 'Maybe it did, maybe it didnt, but we still got here.') +``` + +### yield_for +Another use case I have come across is when creating integration tests and you want to verify that a complex interaction ends with an expected result. In this case you might have an idea of how long the interaction will take to play out but you don't have a signal that you can attach to. Instead you want to pause your test execution until that time has elapsed. For this, Gut has the `yield_for` method. For example `yield(yield_for(5), YIELD)` will pause your test execution for 5 seconds while the rest of your code executes as expected. You must make sure the 2nd parameter to `yield` is the `YIELD` constant. This constant is available to all test scripts. How all this magic works is covered a couple of sections down. Here's an example of yielding for 5 seconds. ``` python @@ -790,6 +801,8 @@ func test_wait_for_a_bit(): yield(yield_for(5), YIELD) gut.assert_eq(my_object.some_property, 'some value', 'After waiting 5 seconds, this property should be set') ``` + +### pause_before_teardown Sometimes it's also helpful to just watch things play out. Yield is great for that, you just create a couple objects, set them to interact and then yield. You can leave the yields in or take them out if your test passes without them. You can also use the `pause_before_teardown` method that will pause test execution before it runs `teardown` and moves onto the next test. This keeps the game loop running after the test has finished and you can see what everything looks like. ### How Yielding and Gut Works @@ -799,6 +812,8 @@ If you only yielded using `yield_for` then Gut would always know when to resume The `yield_for()` method and `YIELD` constant are some syntax sugar built into the `Test` object. `yield` takes in an object and a signal. The `yield_for` method kicks off a timer inside Gut that will run for however many seconds you passed in. It also returns the Gut object so that `yield` has an object to yield to. The `YIELD` constant contains the name of the signal that Gut emits when the timer finishes. +`yield_to` works similarly to `yield_for` except it takes the extra step that Gut will watch the signal you pass in. It will emit the same signal (`YIELD`) when it detects the signal you specified or it will emit the signal when the timer times out. + # Running Gut from the Command Line Also supplied in this repo is the gut_cmdln.gd script that can be run from the command line so that you don't have to create a scene to run your tests. One of the main reasons to use this approach instead of going through the editor is that you get to see error messages generated by Godot in the context of your running tests. You also see any `print` statements you put in your code in the context of all the Gut generated output. It's a bit quicker to get started and is a bit cooler if I do say so. The biggest downside is that debugging your code/tests is a little more difficult since you won't be able to interact with the editor when something blows up. diff --git a/addons/gut/gut.gd b/addons/gut/gut.gd index 64de3436..5b24da17 100644 --- a/addons/gut/gut.gd +++ b/addons/gut/gut.gd @@ -217,8 +217,7 @@ func _yielding_callback(from_obj=false): # to assert that obj/signal_name was emitted. Without this extra delay # the yield returns and processing finishes before the rest of the # objects can get the signal. This works b/c the timer will timeout - # and come back into this method but the obj and signal_name will be - # null + # and come back into this method but from_obj will be false. _yield_timer.set_wait_time(.1) _yield_timer.start() else: From d272bfedf647a4798cb3a154ff63bb4ac4649656 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Sat, 3 Mar 2018 18:36:00 -0500 Subject: [PATCH 10/23] removed testing dir --- scenes/main.tscn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scenes/main.tscn b/scenes/main.tscn index e8dbbe4a..5b03da5b 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -44,7 +44,7 @@ _file_prefix = "test_" _file_extension = ".gd" _directory1 = "res://test/unit" _directory2 = "res://test/integration" -_directory3 = "res://test/samples" +_directory3 = "" _directory4 = "" _directory5 = "" _directory6 = "" From c16d7dc6bd323163ec4308c28b9135eeebb6e741 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Sat, 3 Mar 2018 18:39:23 -0500 Subject: [PATCH 11/23] call _physics_process instead of _fixed_process in simulate --- addons/gut/gut.gd | 4 ++-- test/unit/test_gut.gd | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/addons/gut/gut.gd b/addons/gut/gut.gd index 5b24da17..ac707d4a 100644 --- a/addons/gut/gut.gd +++ b/addons/gut/gut.gd @@ -834,8 +834,8 @@ func simulate(obj, times, delta): for i in range(times): if(obj.has_method("_process")): obj._process(delta) - if(obj.has_method("_fixed_process")): - obj._fixed_process(delta) + if(obj.has_method("_physics_process")): + obj._physics_process(delta) for kid in obj.get_children(): simulate(kid, 1, delta) diff --git a/test/unit/test_gut.gd b/test/unit/test_gut.gd index ba68d869..ada03fe9 100644 --- a/test/unit/test_gut.gd +++ b/test/unit/test_gut.gd @@ -23,13 +23,13 @@ class HasProcessMethod: #Used to test calling the _fixed_process #method on an object through gut #-------------------------------------- -class HasFixedProcessMethod: +class HasPhysicsProcessMethod: extends Node - var fixed_process_called_count = 0 + var physics_process_called_count = 0 var delta_sum = 0.0 - func _fixed_process(delta): - fixed_process_called_count += 1 + func _physics_process(delta): + physics_process_called_count += 1 delta_sum += delta #------------------------------ @@ -216,10 +216,10 @@ func test_simulate_calls_process_on_child_objects_of_child_objects(): assert_pass(objs.size()) -func test_simulate_calls_fixed_process(): - var obj = HasFixedProcessMethod.new() +func test_simulate_calls_physics_process(): + var obj = HasPhysicsProcessMethod.new() gr.test_gut.simulate(obj, 10, .1) - gr.test.assert_eq(obj.fixed_process_called_count, 10, "_process should have been called 10 times") + gr.test.assert_eq(obj.physics_process_called_count, 10, "_process should have been called 10 times") #using just the numbers didn't work, nor using float. str worked for some reason and #i'm not sure why. gr.test.assert_eq(str(obj.delta_sum), str(1), "The delta value should have been passed in and summed") From 7cfbceb4f02bf6a98c65dcde7ce53bc0a3899135 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Sat, 3 Mar 2018 18:40:58 -0500 Subject: [PATCH 12/23] change log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 0fd9b6ef..2695cc84 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). # 6.1.0 * Fixed resize window handle bug. It was connecting to wrong signals and didn't work. +* Missed changing `simulate` to call `_physics_process` instead of `_fixed_process` in the 3.0 conversion. Fixed that. * Improved summary. It now lists all failures and pendings instead of just listing the scripts that have failures or pending tests. * Added `yield_to` which allows you to `yield` to a signal or a maximum amount of time. This keeps your tests moving along if you yield to a signal that never gets emitted. Now the test will fail after an amount of time instead of sitting in limbo forever. This will also watch the signals on the object so you can make asserts about signals after the `yield` and you can save a line of code. Example: From d899d5f5521fb975585ad20f3bffb94e342abc16 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Sat, 3 Mar 2018 20:19:59 -0500 Subject: [PATCH 13/23] dont try to disconnect from freed objects --- addons/gut/signal_watcher.gd | 6 ++++-- test/unit/test_signal_watcher.gd | 13 ++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/addons/gut/signal_watcher.gd b/addons/gut/signal_watcher.gd index 943fd8f2..93d3ea0e 100644 --- a/addons/gut/signal_watcher.gd +++ b/addons/gut/signal_watcher.gd @@ -54,7 +54,7 @@ const ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_' var _watched_signals = {} func _add_watched_signal(obj, name): - # SHORTCIRCUIT + # SHORTCIRCUIT - ignore dupes if(_watched_signals.has(obj) and _watched_signals[obj].has(name)): return @@ -149,5 +149,7 @@ func is_watching(object, signal_name): func clear(): for obj in _watched_signals: for signal_name in _watched_signals[obj]: - obj.disconnect(signal_name, self, '_on_watched_signal') + var wr = weakref(obj) + if(wr.get_ref()): + obj.disconnect(signal_name, self, '_on_watched_signal') _watched_signals.clear() diff --git a/test/unit/test_signal_watcher.gd b/test/unit/test_signal_watcher.gd index df36b57a..f6ef2704 100644 --- a/test/unit/test_signal_watcher.gd +++ b/test/unit/test_signal_watcher.gd @@ -29,6 +29,8 @@ const SIGNALS = { # A class that can emit all the signals in SIGNALS # #################### class SignalObject: + extends Node2D + func _init(): add_user_signal(SIGNALS.NO_PARAMETERS) add_user_signal(SIGNALS.ONE_PARAMETER, [ @@ -221,7 +223,7 @@ func test_watch_signals_ignores_duplicates(): gr.sw.watch_signals(gr.so) gut.p("-- LOOK FOR RED HERE --") assert_true(true) - + # #################### # Clear # #################### @@ -236,3 +238,12 @@ func test_when_cleared_it_should_disconnect_from_signals(): gr.sw.clear() for sig in SIGNALS: assert_false(gr.so.is_connected(SIGNALS[sig], gr.sw, '_on_watched_signal'), str('it should NOT be connected to: ', SIGNALS[sig])) + +func test_clearing_ignores_freed_objecdts(): + add_child(gr.so) + gr.sw.watch_signals(gr.so) + gr.so.free() + yield(yield_for(0.5), YIELD) + gr.sw.clear() + end_test() + #assert_signal_emitted(gr.so, 'script_signal') From c6e05007f4f155c067d3d667e03d2af4b806752f Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Mon, 5 Mar 2018 21:10:04 -0500 Subject: [PATCH 14/23] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 2695cc84..ba9d2507 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). * Fixed resize window handle bug. It was connecting to wrong signals and didn't work. * Missed changing `simulate` to call `_physics_process` instead of `_fixed_process` in the 3.0 conversion. Fixed that. * Improved summary. It now lists all failures and pendings instead of just listing the scripts that have failures or pending tests. +* Fixed issue where the `signal_watcher` could try to disconnect from a freed object. * Added `yield_to` which allows you to `yield` to a signal or a maximum amount of time. This keeps your tests moving along if you yield to a signal that never gets emitted. Now the test will fail after an amount of time instead of sitting in limbo forever. This will also watch the signals on the object so you can make asserts about signals after the `yield` and you can save a line of code. Example: ``` python From a93847a9beab880838b115cd8a17c89e80daa18e Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Wed, 14 Mar 2018 22:53:22 -0400 Subject: [PATCH 15/23] move files to avoid clashes when installing from asset_library --- .../default_env.tres | 0 icon.png => gut_tests_and_examples/icon.png | Bin .../project.godot | 0 {scenes => gut_tests_and_examples/scenes}/main.tscn | 0 .../scripts}/global.gd | 0 {scripts => gut_tests_and_examples/scripts}/main.gd | 0 .../templates}/test_.gd | 0 .../test_sample_all_passed_integration.gd | 0 ...scirpt_has_a_really_long_name_to_test_display.gd | 0 .../test}/samples/test_readme_examples.gd | 0 .../test}/samples/test_sample_all_passed.gd | 0 .../test}/samples/test_sample_one.gd | 0 .../test}/samples/test_sample_two.gd | 0 .../test}/samples/two_pass_pending_fail.gd | 0 .../test}/test_dir_load/bad_prefix.gd | 0 .../test}/test_dir_load/test_bad_extension.txt | 0 .../test}/test_dir_load/test_samples.gd | 0 .../test}/test_dir_load/test_samples2.gd | 0 .../test}/test_dir_load/test_samples3.gd | 0 .../test}/unit/test_command_line_auto_load.gd | 0 .../test}/unit/test_gut.gd | 0 .../test}/unit/test_gut_yielding.gd | 0 .../test}/unit/test_moved_methods.gd | 0 .../test}/unit/test_print.gd | 0 .../test}/unit/test_signal_watcher.gd | 0 .../test}/unit/test_summary.gd | 0 .../test}/unit/test_test.gd | 0 .../test}/unit/verify_signal_watches_are_cleared.gd | 0 28 files changed, 0 insertions(+), 0 deletions(-) rename default_env.tres => gut_tests_and_examples/default_env.tres (100%) rename icon.png => gut_tests_and_examples/icon.png (100%) rename project.godot => gut_tests_and_examples/project.godot (100%) rename {scenes => gut_tests_and_examples/scenes}/main.tscn (100%) rename {scripts => gut_tests_and_examples/scripts}/global.gd (100%) rename {scripts => gut_tests_and_examples/scripts}/main.gd (100%) rename {templates => gut_tests_and_examples/templates}/test_.gd (100%) rename {test => gut_tests_and_examples/test}/integration/test_sample_all_passed_integration.gd (100%) rename {test => gut_tests_and_examples/test}/integration/test_this_scirpt_has_a_really_long_name_to_test_display.gd (100%) rename {test => gut_tests_and_examples/test}/samples/test_readme_examples.gd (100%) rename {test => gut_tests_and_examples/test}/samples/test_sample_all_passed.gd (100%) rename {test => gut_tests_and_examples/test}/samples/test_sample_one.gd (100%) rename {test => gut_tests_and_examples/test}/samples/test_sample_two.gd (100%) rename {test => gut_tests_and_examples/test}/samples/two_pass_pending_fail.gd (100%) rename {test => gut_tests_and_examples/test}/test_dir_load/bad_prefix.gd (100%) rename {test => gut_tests_and_examples/test}/test_dir_load/test_bad_extension.txt (100%) rename {test => gut_tests_and_examples/test}/test_dir_load/test_samples.gd (100%) rename {test => gut_tests_and_examples/test}/test_dir_load/test_samples2.gd (100%) rename {test => gut_tests_and_examples/test}/test_dir_load/test_samples3.gd (100%) rename {test => gut_tests_and_examples/test}/unit/test_command_line_auto_load.gd (100%) rename {test => gut_tests_and_examples/test}/unit/test_gut.gd (100%) rename {test => gut_tests_and_examples/test}/unit/test_gut_yielding.gd (100%) rename {test => gut_tests_and_examples/test}/unit/test_moved_methods.gd (100%) rename {test => gut_tests_and_examples/test}/unit/test_print.gd (100%) rename {test => gut_tests_and_examples/test}/unit/test_signal_watcher.gd (100%) rename {test => gut_tests_and_examples/test}/unit/test_summary.gd (100%) rename {test => gut_tests_and_examples/test}/unit/test_test.gd (100%) rename {test => gut_tests_and_examples/test}/unit/verify_signal_watches_are_cleared.gd (100%) diff --git a/default_env.tres b/gut_tests_and_examples/default_env.tres similarity index 100% rename from default_env.tres rename to gut_tests_and_examples/default_env.tres diff --git a/icon.png b/gut_tests_and_examples/icon.png similarity index 100% rename from icon.png rename to gut_tests_and_examples/icon.png diff --git a/project.godot b/gut_tests_and_examples/project.godot similarity index 100% rename from project.godot rename to gut_tests_and_examples/project.godot diff --git a/scenes/main.tscn b/gut_tests_and_examples/scenes/main.tscn similarity index 100% rename from scenes/main.tscn rename to gut_tests_and_examples/scenes/main.tscn diff --git a/scripts/global.gd b/gut_tests_and_examples/scripts/global.gd similarity index 100% rename from scripts/global.gd rename to gut_tests_and_examples/scripts/global.gd diff --git a/scripts/main.gd b/gut_tests_and_examples/scripts/main.gd similarity index 100% rename from scripts/main.gd rename to gut_tests_and_examples/scripts/main.gd diff --git a/templates/test_.gd b/gut_tests_and_examples/templates/test_.gd similarity index 100% rename from templates/test_.gd rename to gut_tests_and_examples/templates/test_.gd diff --git a/test/integration/test_sample_all_passed_integration.gd b/gut_tests_and_examples/test/integration/test_sample_all_passed_integration.gd similarity index 100% rename from test/integration/test_sample_all_passed_integration.gd rename to gut_tests_and_examples/test/integration/test_sample_all_passed_integration.gd diff --git a/test/integration/test_this_scirpt_has_a_really_long_name_to_test_display.gd b/gut_tests_and_examples/test/integration/test_this_scirpt_has_a_really_long_name_to_test_display.gd similarity index 100% rename from test/integration/test_this_scirpt_has_a_really_long_name_to_test_display.gd rename to gut_tests_and_examples/test/integration/test_this_scirpt_has_a_really_long_name_to_test_display.gd diff --git a/test/samples/test_readme_examples.gd b/gut_tests_and_examples/test/samples/test_readme_examples.gd similarity index 100% rename from test/samples/test_readme_examples.gd rename to gut_tests_and_examples/test/samples/test_readme_examples.gd diff --git a/test/samples/test_sample_all_passed.gd b/gut_tests_and_examples/test/samples/test_sample_all_passed.gd similarity index 100% rename from test/samples/test_sample_all_passed.gd rename to gut_tests_and_examples/test/samples/test_sample_all_passed.gd diff --git a/test/samples/test_sample_one.gd b/gut_tests_and_examples/test/samples/test_sample_one.gd similarity index 100% rename from test/samples/test_sample_one.gd rename to gut_tests_and_examples/test/samples/test_sample_one.gd diff --git a/test/samples/test_sample_two.gd b/gut_tests_and_examples/test/samples/test_sample_two.gd similarity index 100% rename from test/samples/test_sample_two.gd rename to gut_tests_and_examples/test/samples/test_sample_two.gd diff --git a/test/samples/two_pass_pending_fail.gd b/gut_tests_and_examples/test/samples/two_pass_pending_fail.gd similarity index 100% rename from test/samples/two_pass_pending_fail.gd rename to gut_tests_and_examples/test/samples/two_pass_pending_fail.gd diff --git a/test/test_dir_load/bad_prefix.gd b/gut_tests_and_examples/test/test_dir_load/bad_prefix.gd similarity index 100% rename from test/test_dir_load/bad_prefix.gd rename to gut_tests_and_examples/test/test_dir_load/bad_prefix.gd diff --git a/test/test_dir_load/test_bad_extension.txt b/gut_tests_and_examples/test/test_dir_load/test_bad_extension.txt similarity index 100% rename from test/test_dir_load/test_bad_extension.txt rename to gut_tests_and_examples/test/test_dir_load/test_bad_extension.txt diff --git a/test/test_dir_load/test_samples.gd b/gut_tests_and_examples/test/test_dir_load/test_samples.gd similarity index 100% rename from test/test_dir_load/test_samples.gd rename to gut_tests_and_examples/test/test_dir_load/test_samples.gd diff --git a/test/test_dir_load/test_samples2.gd b/gut_tests_and_examples/test/test_dir_load/test_samples2.gd similarity index 100% rename from test/test_dir_load/test_samples2.gd rename to gut_tests_and_examples/test/test_dir_load/test_samples2.gd diff --git a/test/test_dir_load/test_samples3.gd b/gut_tests_and_examples/test/test_dir_load/test_samples3.gd similarity index 100% rename from test/test_dir_load/test_samples3.gd rename to gut_tests_and_examples/test/test_dir_load/test_samples3.gd diff --git a/test/unit/test_command_line_auto_load.gd b/gut_tests_and_examples/test/unit/test_command_line_auto_load.gd similarity index 100% rename from test/unit/test_command_line_auto_load.gd rename to gut_tests_and_examples/test/unit/test_command_line_auto_load.gd diff --git a/test/unit/test_gut.gd b/gut_tests_and_examples/test/unit/test_gut.gd similarity index 100% rename from test/unit/test_gut.gd rename to gut_tests_and_examples/test/unit/test_gut.gd diff --git a/test/unit/test_gut_yielding.gd b/gut_tests_and_examples/test/unit/test_gut_yielding.gd similarity index 100% rename from test/unit/test_gut_yielding.gd rename to gut_tests_and_examples/test/unit/test_gut_yielding.gd diff --git a/test/unit/test_moved_methods.gd b/gut_tests_and_examples/test/unit/test_moved_methods.gd similarity index 100% rename from test/unit/test_moved_methods.gd rename to gut_tests_and_examples/test/unit/test_moved_methods.gd diff --git a/test/unit/test_print.gd b/gut_tests_and_examples/test/unit/test_print.gd similarity index 100% rename from test/unit/test_print.gd rename to gut_tests_and_examples/test/unit/test_print.gd diff --git a/test/unit/test_signal_watcher.gd b/gut_tests_and_examples/test/unit/test_signal_watcher.gd similarity index 100% rename from test/unit/test_signal_watcher.gd rename to gut_tests_and_examples/test/unit/test_signal_watcher.gd diff --git a/test/unit/test_summary.gd b/gut_tests_and_examples/test/unit/test_summary.gd similarity index 100% rename from test/unit/test_summary.gd rename to gut_tests_and_examples/test/unit/test_summary.gd diff --git a/test/unit/test_test.gd b/gut_tests_and_examples/test/unit/test_test.gd similarity index 100% rename from test/unit/test_test.gd rename to gut_tests_and_examples/test/unit/test_test.gd diff --git a/test/unit/verify_signal_watches_are_cleared.gd b/gut_tests_and_examples/test/unit/verify_signal_watches_are_cleared.gd similarity index 100% rename from test/unit/verify_signal_watches_are_cleared.gd rename to gut_tests_and_examples/test/unit/verify_signal_watches_are_cleared.gd From d7b79095fbef6d134617564ef44019743005ccc9 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Wed, 14 Mar 2018 23:43:50 -0400 Subject: [PATCH 16/23] move LICENSE.md --- README.md | 3 +++ LICENSE.md => addons/gut/LICENSE.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) rename LICENSE.md => addons/gut/LICENSE.md (96%) diff --git a/README.md b/README.md index edd40094..9dc50c7d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ GUT (Godot Unit Test) is a utility for writing tests for your Godot Engine game. ### Godot 3.0 Compatible. Version 6.0.0 is Godot 3.0 compatible. These changes are not compatible with any of the 2.x versions of Godot. The godot_2x branch has been created to hold the old version of Gut that works with Godot 2.x. Barring any severe issues, there will not be any more development for Godot 2.x. +# License +Gut is provided under the MIT license. [The license is distributed with Gut so it is in the `addons/gut` folder](addons/gut/LICENSE.md). + # Method Links
diff --git a/LICENSE.md b/addons/gut/LICENSE.md similarity index 96% rename from LICENSE.md rename to addons/gut/LICENSE.md index e94c2b14..a38ac231 100644 --- a/LICENSE.md +++ b/addons/gut/LICENSE.md @@ -1,7 +1,7 @@ The MIT License (MIT) ===================== -Copyright (c) 2017 Tom "Butch" Wesley +Copyright (c) 2018 Tom "Butch" Wesley Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From a9d991e2176d72112ce4ad8006246b1a4b86c815 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Wed, 14 Mar 2018 23:45:23 -0400 Subject: [PATCH 17/23] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9dc50c7d..920c2093 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ GUT (Godot Unit Test) is a utility for writing tests for your Godot Engine game. Version 6.0.0 is Godot 3.0 compatible. These changes are not compatible with any of the 2.x versions of Godot. The godot_2x branch has been created to hold the old version of Gut that works with Godot 2.x. Barring any severe issues, there will not be any more development for Godot 2.x. # License -Gut is provided under the MIT license. [The license is distributed with Gut so it is in the `addons/gut` folder](addons/gut/LICENSE.md). +Gut is provided under the MIT license. [The license is distributed with Gut so it is in the `addons/gut` folder](addons/gut/LICENSE.md). I also didn't want the Gut license to accidentally be copied into another project when installed through the Godot Asset Library. # Method Links From 7a9db04dfd605f2f022600cd8954171fc6a1d2f1 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Wed, 14 Mar 2018 23:46:49 -0400 Subject: [PATCH 18/23] update links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 920c2093..0276cc76 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Each test should perform at least one assert or call `pending` to indicate the t # Test Related Methods -These methods should be used in tests to make assertions. These methods are available to anything that inherits from the Test class (`extends "res://addons/gut/test.gd"`). All sample code listed for the methods can be found here in [test_readme_examples.gd](test/samples/test_readme_examples.gd) +These methods should be used in tests to make assertions. These methods are available to anything that inherits from the Test class (`extends "res://addons/gut/test.gd"`). All sample code listed for the methods can be found here in [test_readme_examples.gd](gut_tests_and_examples/test/samples/test_readme_examples.gd) #### pending(text="") flag a test as pending, the optional message is printed in the GUI ``` python @@ -889,7 +889,7 @@ I got this one when I accidentally put a space instead of an "=" after -gselect. # Contributing This testing tool has tests of course. All Gut related tests are found in the `test/unit` and `test/integration` directories. Any enhancements or bug fixes should have a corresponding pull request with new tests. -The bulk of the tests for Gut are in [test_gut.gd](test/unit/test_gut.gd) and [test_test.gd](test/unit/test_test.gd). [test_signal_watcher.gd](test/unit/test_signal_watcher.gd) tests the class used to track the emitting of signals. The other test scripts in `unit` and `integration` should be run and their output spot checked since they test other parts of Gut that aren't easily testabled. +The bulk of the tests for Gut are in [test_gut.gd](gut_tests_and_examples/test/unit/test_gut.gd) and [test_test.gd](gut_tests_and_examples/test/unit/test_test.gd). [test_signal_watcher.gd](gut_tests_and_examples/test/unit/test_signal_watcher.gd) tests the class used to track the emitting of signals. The other test scripts in `unit` and `integration` should be run and their output spot checked since they test other parts of Gut that aren't easily testabled. For convenience, the `main.tscn` includes a handy "Run Gut Unit Tests" button that will kick off all the essential test scripts. From e93c0bb895c73c05c8dfb78f5229ed9ac585f938 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Thu, 15 Mar 2018 20:34:40 -0400 Subject: [PATCH 19/23] changes --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index ba9d2507..c4c21f74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). # 6.1.0 +* Moved as many files as I could to `gut_tests_and_examples` so that there was less stuff to uncheck when installing via the in-engine Asset Library. I'm still not 100% happy with the setup. * Fixed resize window handle bug. It was connecting to wrong signals and didn't work. * Missed changing `simulate` to call `_physics_process` instead of `_fixed_process` in the 3.0 conversion. Fixed that. * Improved summary. It now lists all failures and pendings instead of just listing the scripts that have failures or pending tests. From 8b44fb63bb9f5285606addc0b5746bdd37a707f2 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Thu, 15 Mar 2018 20:57:11 -0400 Subject: [PATCH 20/23] yield_to examples and documentation --- README.md | 46 ++++++++++++++++++- .../test/samples/test_readme_examples.gd | 42 ++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0276cc76..5c09f735 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Gut 6.0.0 +# Gut 6.1.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. ### Godot 3.0 Compatible. @@ -612,6 +612,7 @@ Print info to the GUI and console (if enabled). You can see examples if this in #### gut.pause_before_teardown() This method will cause Gut to pause before it moves on to the next test. This is useful for debugging, for instance if you want to investigate the screen or anything else after a test has finished executing. See also `set_ignore_pause_before_teardown` + #### yield_for(time_in_seconds) This simplifies the code needed to pause the test execution for a number of seconds so the thing that you are testing can run its course in real time. There are more details in the Yielding section. It is designed to be used with the `yield` built in. The following example will pause your test execution (and only the test execution) for 2 seconds before continuing. You must call an assert or `pending` or `end_test()` after a yield or the test will never stop running. ``` python @@ -635,6 +636,49 @@ func test_illustrate_yield(): assert_gt(moving_node.get_pos().x, 0) assert_between(moving_node.get_pos().x, 3.9, 4, 'it should move almost 4 whatevers at speed 2') ``` + +#### yield_to(object, signal_name, max_time) +`yield_to` allows you to yield to a signal just like `yield` but for a maximum amount of time. This keeps tests moving along when signals are not emitted. Just like with any test that has a yield in it, you must call an assert or `pending` or `end_test()` after a yield or the test will never stop running. + +As a bonus, `yield_to` does an implicit call to `watch_signals` so you can easily make signal based assertions afterwards. +``` python +class TimedSignaler: + extends Node2D + var _time = 0 + + signal the_signal + func _init(time): + _time = time + + func start(): + var t = Timer.new() + add_child(t) + t.set_wait_time(_time) + t.connect('timeout', self, '_on_timer_timeout') + t.set_one_shot(true) + t.start() + + func _on_timer_timeout(): + emit_signal('the_signal') + +func test_illustrate_yield_to_with_less_time(): + var t = TimedSignaler.new(5) + add_child(t) + t.start() + yield(yield_to(t, 'the_signal', 1), YIELD) + # since we setup t to emit after 5 seconds, this will fail because we + # only yielded for 1 second vai yield_to + assert_signal_emitted(t, 'the_signal', 'This will fail') + +func test_illustrate_yield_to_with_more_time(): + var t = TimedSignaler.new(1) + add_child(t) + t.start() + yield(yield_to(t, 'the_signal', 5), YIELD) + # since we wait longer than it will take to emit the signal, this assert + # will pass + assert_signal_emitted(t, 'the_signal', 'This will pass') +``` #### end_test() This is a holdover from previous versions. You should probably use an assert or `pending` to close out a yielded test but you can use this instead if you really really want to. ``` diff --git a/gut_tests_and_examples/test/samples/test_readme_examples.gd b/gut_tests_and_examples/test/samples/test_readme_examples.gd index fe3c9d87..fb76a746 100644 --- a/gut_tests_and_examples/test/samples/test_readme_examples.gd +++ b/gut_tests_and_examples/test/samples/test_readme_examples.gd @@ -141,7 +141,6 @@ func test_assert_file_exists(): assert_file_exists('user://file_does_not.exist') # FAIL assert_file_exists('res://some_dir/another_dir/file_does_not.exist') # FAIL - func test_assert_file_does_not_exist(): gut.p('-- passing --') assert_file_does_not_exist('user://file_does_not.exist') # PASS @@ -165,6 +164,7 @@ func test_assert_file_not_empty(): gut.p('-- failing --') assert_file_not_empty('user://some_test_file') # FAIL +# ------------------------------------------------------------------------------ class SomeClass: var _count = 0 @@ -191,6 +191,7 @@ func test_assert_get_set_methods(): # 2 FAILING assert_get_set_methods(some_class, 'does_not_exist', 'does_not', 'matter') +# ------------------------------------------------------------------------------ class MovingNode: extends Node2D var _speed = 2 @@ -218,6 +219,44 @@ func test_illustrate_end_test(): # finished. end_test() +# ------------------------------------------------------------------------------ +class TimedSignaler: + extends Node2D + var _time = 0 + + signal the_signal + func _init(time): + _time = time + + func start(): + var t = Timer.new() + add_child(t) + t.set_wait_time(_time) + t.connect('timeout', self, '_on_timer_timeout') + t.set_one_shot(true) + t.start() + + func _on_timer_timeout(): + emit_signal('the_signal') + +func test_illustrate_yield_to_with_less_time(): + var t = TimedSignaler.new(5) + add_child(t) + t.start() + yield(yield_to(t, 'the_signal', 1), YIELD) + # since we setup t to emit after 5 seconds, this will fail because we + # only yielded for 1 second vail yield_to + assert_signal_emitted(t, 'the_signal', 'This will fail') + +func test_illustrate_yield_to_with_more_time(): + var t = TimedSignaler.new(1) + add_child(t) + t.start() + yield(yield_to(t, 'the_signal', 5), YIELD) + # since we wait longer than it will take to emit the signal, this assert + # will pass + assert_signal_emitted(t, 'the_signal', 'This will pass') +# ------------------------------------------------------------------------------ class SignalObject: func _init(): add_user_signal('some_signal') @@ -346,6 +385,7 @@ func test_get_signal_parameters(): assert_eq(get_signal_parameters(obj, 'some_signal'), [1, 2, 3]) assert_eq(get_signal_parameters(obj, 'some_signal', 0), ['a', 'b', 'c']) +# ------------------------------------------------------------------------------ class BaseClass: var a = 1 class SubClass: From d8a26a2ea3fed958c5244e30f276511aee825c83 Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Thu, 15 Mar 2018 21:02:04 -0400 Subject: [PATCH 21/23] license placement info --- CHANGES.md | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c4c21f74..7f947353 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). # 6.1.0 * Moved as many files as I could to `gut_tests_and_examples` so that there was less stuff to uncheck when installing via the in-engine Asset Library. I'm still not 100% happy with the setup. +* Moved the License to `addons/gut/` so that it is distributed with the addon and doesn't accidently get copied into the root of some other project when installed via the Asset Library. * Fixed resize window handle bug. It was connecting to wrong signals and didn't work. * Missed changing `simulate` to call `_physics_process` instead of `_fixed_process` in the 3.0 conversion. Fixed that. * Improved summary. It now lists all failures and pendings instead of just listing the scripts that have failures or pending tests. diff --git a/README.md b/README.md index 5c09f735..3f641f9e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ GUT (Godot Unit Test) is a utility for writing tests for your Godot Engine game. Version 6.0.0 is Godot 3.0 compatible. These changes are not compatible with any of the 2.x versions of Godot. The godot_2x branch has been created to hold the old version of Gut that works with Godot 2.x. Barring any severe issues, there will not be any more development for Godot 2.x. # License -Gut is provided under the MIT license. [The license is distributed with Gut so it is in the `addons/gut` folder](addons/gut/LICENSE.md). I also didn't want the Gut license to accidentally be copied into another project when installed through the Godot Asset Library. +Gut is provided under the MIT license. [The license is distributed with Gut so it is in the `addons/gut` folder](addons/gut/LICENSE.md). I also didn't want the Gut license to accidentally be copied into another project's root directory when installed through the Godot Asset Library. # Method Links
From 974df1faf8053e47126aaaee05287bfd19853ada Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Thu, 15 Mar 2018 21:33:07 -0400 Subject: [PATCH 22/23] fix test paths --- gut_tests_and_examples/scenes/main.tscn | 6 +-- gut_tests_and_examples/scripts/main.gd | 14 +++--- gut_tests_and_examples/test/unit/test_gut.gd | 50 ++++++++++--------- .../project.godot => project.godot | 6 +-- 4 files changed, 37 insertions(+), 39 deletions(-) rename gut_tests_and_examples/project.godot => project.godot (78%) diff --git a/gut_tests_and_examples/scenes/main.tscn b/gut_tests_and_examples/scenes/main.tscn index 5b03da5b..d6e5bf34 100644 --- a/gut_tests_and_examples/scenes/main.tscn +++ b/gut_tests_and_examples/scenes/main.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=4 format=2] -[ext_resource path="res://scripts/main.gd" type="Script" id=1] +[ext_resource path="res://gut_tests_and_examples/scripts/main.gd" type="Script" id=1] [ext_resource path="res://addons/gut/gut.gd" type="Script" id=2] [ext_resource path="res://addons/gut/icon.png" type="Texture" id=3] @@ -42,8 +42,8 @@ _disable_strict_datatype_checks = false _test_prefix = "test_" _file_prefix = "test_" _file_extension = ".gd" -_directory1 = "res://test/unit" -_directory2 = "res://test/integration" +_directory1 = "res://gut_tests_and_examples/test/unit" +_directory2 = "res://gut_tests_and_examples/test/integration" _directory3 = "" _directory4 = "" _directory5 = "" diff --git a/gut_tests_and_examples/scripts/main.gd b/gut_tests_and_examples/scripts/main.gd index 6cb4dc2c..a4c4c3be 100644 --- a/gut_tests_and_examples/scripts/main.gd +++ b/gut_tests_and_examples/scripts/main.gd @@ -57,7 +57,7 @@ func _on_tests_finished(): # be visible in the console, not the Gut instance on the screen. #------------------------------------ func _run_test_one_line(): - load('res://addons/gut/gut.gd').new().test_script('res://test/samples/test_sample_all_passed.gd') + load('res://addons/gut/gut.gd').new().test_script('res://gut_tests_and_examples/test/samples/test_sample_all_passed.gd') #------------------------------------ # More lines, get result text out manually. Can also inspect the results further @@ -82,8 +82,8 @@ func _run_all_tests(): # !! -------- # Add all scripts in two directories. - tester.add_directory('res://test/unit') - tester.add_directory('res://test/integration') + tester.add_directory('res://gut_tests_and_examples/test/unit') + tester.add_directory('res://gut_tests_and_examples/test/integration') # Automatcially run all scripts when loaded. tester.test_scripts(true) @@ -97,10 +97,10 @@ func _run_all_tests(): # the resutls. func _run_gut_tests(gut): gut.set_should_print_to_console(false) - gut.add_script('res://test/unit/test_gut.gd') - gut.add_script('res://test/unit/test_gut_yielding.gd') - gut.add_script('res://test/unit/test_test.gd') - gut.add_script('res://test/unit/test_signal_watcher.gd') + gut.add_script('res://gut_tests_and_examples/test/unit/test_gut.gd') + gut.add_script('res://gut_tests_and_examples/test/unit/test_gut_yielding.gd') + gut.add_script('res://gut_tests_and_examples/test/unit/test_test.gd') + gut.add_script('res://gut_tests_and_examples/test/unit/test_signal_watcher.gd') gut.set_yield_between_tests(true) # true says to run all the scripts, not just the first or # the selected script. diff --git a/gut_tests_and_examples/test/unit/test_gut.gd b/gut_tests_and_examples/test/unit/test_gut.gd index ada03fe9..d62a185f 100644 --- a/gut_tests_and_examples/test/unit/test_gut.gd +++ b/gut_tests_and_examples/test/unit/test_gut.gd @@ -229,18 +229,19 @@ func test_simulate_calls_physics_process(): # ------------------------------ # Setting test to run # ------------------------------ +const SAMPLES_DIR = 'res://gut_tests_and_examples/test/samples/' func test_get_set_test_to_run(): gr.test.assert_get_set_methods(gr.test_gut, 'unit_test_name', '', 'hello') assert_pass(4) func test_setting_name_will_run_only_matching_tests(): - gr.test_gut.add_script('res://test/samples/test_sample_all_passed.gd') + gr.test_gut.add_script(SAMPLES_DIR + 'test_sample_all_passed.gd') gr.test_gut.set_unit_test_name('test_works') gr.test_gut.test_scripts() assert_eq(gr.test_gut.get_test_count(), 1) func test_setting_name_matches_partial(): - gr.test_gut.add_script('res://test/samples/test_sample_all_passed.gd') + gr.test_gut.add_script(SAMPLES_DIR + 'test_sample_all_passed.gd') gr.test_gut.set_unit_test_name('two') gr.test_gut.test_scripts() assert_eq(gr.test_gut.get_test_count(), 1) @@ -277,7 +278,7 @@ func test_asserts_on_test_object(): assert_file_not_empty(path) func test_gut_clears_test_instances_between_runs(): - gr.test_gut.add_script('res://test/samples/test_sample_all_passed.gd') + gr.test_gut.add_script(SAMPLES_DIR + 'test_sample_all_passed.gd') gr.test_gut.test_scripts() gr.test_gut.test_scripts() assert_eq(gr.test_gut._test_script_objects.size(), 1, 'The should only be one test script after a second run') @@ -285,26 +286,27 @@ func test_gut_clears_test_instances_between_runs(): # ------------------------------ # Loading diretories # ------------------------------ +const TEST_LOAD_DIR = 'res://gut_tests_and_examples/test/test_dir_load' func test_adding_directory_loads_files(): - gr.test_gut.add_directory('res://test/test_dir_load') - assert_has(gr.test_gut._test_scripts, 'res://test/test_dir_load/test_samples.gd') + gr.test_gut.add_directory(TEST_LOAD_DIR) + assert_has(gr.test_gut._test_scripts, TEST_LOAD_DIR + '/test_samples.gd') func test_adding_directory_does_not_load_bad_prefixed_files(): - gr.test_gut.add_directory('res://test/test_dir_load') - assert_does_not_have(gr.test_gut._test_scripts, 'res://test/test_dir_load/bad_prefix.gd') + gr.test_gut.add_directory(TEST_LOAD_DIR) + assert_does_not_have(gr.test_gut._test_scripts, TEST_LOAD_DIR + '/bad_prefix.gd') func test_adding_directory_skips_files_with_wrong_extension(): - gr.test_gut.add_directory('res://test/test_dir_load') - assert_does_not_have(gr.test_gut._test_scripts, 'res://test/test_dir_load/test_bad_extension.txt') + gr.test_gut.add_directory(TEST_LOAD_DIR) + assert_does_not_have(gr.test_gut._test_scripts, TEST_LOAD_DIR + '/test_bad_extension.txt') func test_if_directory_does_not_exist_it_does_not_die(): gr.test_gut.add_directory('res://adsf') assert_true(true, 'We should get here') func test_adding_same_directory_does_not_add_duplicates(): - gr.test_gut.add_directory('res://test/unit') + gr.test_gut.add_directory('res://gut_tests_and_examples/test/unit') var orig = gr.test_gut._test_scripts.size() - gr.test_gut.add_directory('res://test/unit') + gr.test_gut.add_directory('res://gut_tests_and_examples/test/unit') assert_eq(gr.test_gut._test_scripts.size(), orig) # We only have 3 directories with tests in them so test 3 @@ -313,13 +315,13 @@ func test_directories123_defined_in_editor_are_loaded_on_ready(): var t = Test.new() t.gut = g g.set_yield_between_tests(false) - g._directory1 = 'res://test/test_dir_load' - g._directory2 = 'res://test/unit' - g._directory3 = 'res://test/integration' + g._directory1 = 'res://gut_tests_and_examples/test/test_dir_load' + g._directory2 = 'res://gut_tests_and_examples/test/unit' + g._directory3 = 'res://gut_tests_and_examples/test/integration' add_child(g) - t.assert_has(g._test_scripts, 'res://test/test_dir_load/test_samples.gd', 'Should have dir1 script') - t.assert_has(g._test_scripts, 'res://test/unit/test_gut.gd', 'Should have dir2 script') - t.assert_has(g._test_scripts, 'res://test/integration/test_sample_all_passed_integration.gd', 'Should have dir3 script') + t.assert_has(g._test_scripts, 'res://gut_tests_and_examples/test/test_dir_load/test_samples.gd', 'Should have dir1 script') + t.assert_has(g._test_scripts, 'res://gut_tests_and_examples/test/unit/test_gut.gd', 'Should have dir2 script') + t.assert_has(g._test_scripts, 'res://gut_tests_and_examples/test/integration/test_sample_all_passed_integration.gd', 'Should have dir3 script') assert_eq(t.get_pass_count(), 3, 'they should have passed') # ^ aaaand then we test 2 more. @@ -328,20 +330,20 @@ func test_directories456_defined_in_editor_are_loaded_on_ready(): var t = Test.new() t.gut = g g.set_yield_between_tests(false) - g._directory4 = 'res://test/test_dir_load' - g._directory5 = 'res://test/unit' - g._directory6 = 'res://test/integration' + g._directory4 = 'res://gut_tests_and_examples/test/test_dir_load' + g._directory5 = 'res://gut_tests_and_examples/test/unit' + g._directory6 = 'res://gut_tests_and_examples/test/integration' add_child(g) - t.assert_has(g._test_scripts, 'res://test/test_dir_load/test_samples.gd', 'Should have dir4 script') - t.assert_has(g._test_scripts, 'res://test/unit/test_gut.gd', 'Should have dir5 script') - t.assert_has(g._test_scripts, 'res://test/integration/test_sample_all_passed_integration.gd', 'Should have dir6 script') + t.assert_has(g._test_scripts, 'res://gut_tests_and_examples/test/test_dir_load/test_samples.gd', 'Should have dir4 script') + t.assert_has(g._test_scripts, 'res://gut_tests_and_examples/test/unit/test_gut.gd', 'Should have dir5 script') + t.assert_has(g._test_scripts, 'res://gut_tests_and_examples/test/integration/test_sample_all_passed_integration.gd', 'Should have dir6 script') assert_eq(t.get_pass_count(), 3, 'they should have passed') # ------------------------------ # Signal tests # ------------------------------ func test_when_moving_to_next_test_watched_signals_are_cleared(): - gr.test_gut.add_script('res://test/unit/verify_signal_watches_are_cleared.gd') + gr.test_gut.add_script('res://gut_tests_and_examples/test/unit/verify_signal_watches_are_cleared.gd') gr.test_gut.test_scripts() assert_eq(gr.test_gut.get_pass_count(), 1, 'One test should have passed.') assert_eq(gr.test_gut.get_fail_count(), 1, 'One failure for not watching anymore.') diff --git a/gut_tests_and_examples/project.godot b/project.godot similarity index 78% rename from gut_tests_and_examples/project.godot rename to project.godot index 41a1c353..a37af309 100644 --- a/gut_tests_and_examples/project.godot +++ b/project.godot @@ -15,12 +15,8 @@ config/icon="res://icon.png" [autoload] -global="*res://scripts/global.gd" +global="*res://gut_tests_and_examples/scripts/global.gd" [editor_plugins] enabled=PoolStringArray( "gut" ) - -[rendering] - -environment/default_environment="res://default_env.tres" From c6507f5fc259eb01525e335dfc6b8a6a76b0ab8d Mon Sep 17 00:00:00 2001 From: Butch Wesley Date: Thu, 15 Mar 2018 21:53:39 -0400 Subject: [PATCH 23/23] AssetLib install instructions --- CHANGES.md | 1 + README.md | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7f947353..84b84e5a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). # 6.1.0 * Moved as many files as I could to `gut_tests_and_examples` so that there was less stuff to uncheck when installing via the in-engine Asset Library. I'm still not 100% happy with the setup. * Moved the License to `addons/gut/` so that it is distributed with the addon and doesn't accidently get copied into the root of some other project when installed via the Asset Library. +* Some README tweaks. * Fixed resize window handle bug. It was connecting to wrong signals and didn't work. * Missed changing `simulate` to call `_physics_process` instead of `_fixed_process` in the 3.0 conversion. Fixed that. * Improved summary. It now lists all failures and pendings instead of just listing the scripts that have failures or pending tests. diff --git a/README.md b/README.md index 3f641f9e..c1fdb53f 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,22 @@ Gut is provided under the MIT license. [The license is distributed with Gut so 1. [Contributing](#contributing) # Install -## New Installs and Upgrades +## Installing from Download Download and extract the zip from the [releases](https://github.com/bitwes/gut/releases) or from the [Godot Asset Library](https://godotengine.org/asset-library/asset/54). Extract the zip and place the `gut` directory into your `addons` directory in your project. If you don't have an `addons` folder at the root of your project, then make one and THEN put the `gut` directory in there. -## New Install Configuration +## Installing from in-editor Godot Asset Lib +1. Click the AssetLib button at the top of the editor +1. Search for "Gut" +1. Click it. +1. Click "Install". This will kick off the download. +1. Click the 2nd "Install" button that appears when the download finishes. It will be in a little dialog at the bottom of the AssetLib window. +1. This part is IMPORTANT. You only need the `addons/gut` directory. So make sure that directory is checked then uncheck anything that isn't in the `addons/gut` directory. +1. Click the 3rd "Install" button. +1. You did it! + +## New Install Setup From the menu choose Scene->Project Settings, click the plugins tab and activate Gut. The next few steps cover the suggested configuration. Feel free to deviate where you see fit.