diff --git a/CHANGES.md b/CHANGES.md index 97d5347d..fae97f67 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +# 5.0.0 +This version mostly contains some long overdue house cleaning. When I first created Gut I tried to keep it all to a single file so that deployment was easier. With the advent of the Addons system, I have a lot more freedom in structuring the files. I had also based the structure of the classes on another unit test tool I had cobbled together for a procedural language. This refactoring of the files will make changes easier in the future and separates out the various responsibilities into their own files and classes better. + +So that this wasn't JUST a reorganization release I also added an method for asserting instance type and inheritance. + +### Breaking Changes from 4.1.0 +Due to the restructuring I've completely moved the various `asserts` out of the core gut object and put them in the test object that all unit tests inherit from. This means that any asserts or pending calls that are prefixed with `gut.` need to have the `gut.` prefix removed. To cut down on the annoyance level of this change, I've added the methods back into gut but they fail with a message indicating that the method has been moved. + +- New Methdos + - `assert_extends` Asserts that an instance of an object inherits from the class passed. +- Some changes to the log output. + - Quick summary about each test script is included at the end of the run. + - Scripts that had a failing assert are listed together in the quick summary. +- Changed the GUI to have a fixed width font. It makes formatting the output easier and I like it more. Future changes should make customizing the GUI possible, so if you aren't fond of it you'll be able to change it sometime soon. +- All asserts were moved from the `gut` class to the `test` class so you don't need to prefix them. Placeholder methods were put back into `gut` so your tests will run but fail with a message indicating the assert has been moved. + + + + # 4.1.0 - Added the ability to assert that signals did or did not fire. By calling `watch_signals` Gut will monitor any signals emitted by the object. You can then later make assertions about whether or not a signal was emitted or whether it emitted a desired number of times, and even if it was emitted and passed specific parameters. You can also verify that the signal was emitted with the right parameters. The following methods were added for this feature, see the README for more information about them. - `watch_signals` @@ -26,8 +45,5 @@ __Note:__ just about everything you had to code to get your main testing scene 0. The object that all test scripts must extend has changed to `res://addons/gut/test.gd`. 0. All examples and tests for Gut itself have been moved to the new repo https://github.com/bitwes/GutTests/ -### Other Changes -- I think there were other changes that went into this but I started keeping a change log with 4.0.1. If it is really important to you then you could probably figure it out through the history. - -# Earlier versions -- I didn't track changes for earlier versions. I know I should have. Trust me, you can't make me feel any worse about it so just get off my back already. +### Earlier Versions: +- There were earlier versions, they had changes but I can't remember what they were. diff --git a/README.md b/README.md index 032cde0c..8b755c5f 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,65 @@ # Gut 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. -## 4.x.x Breaking changes from 3.0.x -In 4.0.0 Gut was changed to be a plugin. This has some setup ramifications. __These changes only apply when upgrading from a version earlier than 4.0.0__. See the 4.0.0 section in CHANGES.md for upgrade information. +### Breaking Changes from 4.1.0 +Due to the restructuring I've completely moved the various `asserts` out of the core gut object and put them in the test object that all unit tests inherit from. This means that any asserts or pending calls that are prefixed with `gut.` need to have the `gut.` prefix removed. To cut down on the annoyance level of this change, I've added the methods back into gut but they fail with a message indicating that the method has been moved. + +# Method Links + + +
+ +[assert_between](#assert_between)
+[assert_does_not_have](#assert_does_not_have)
+[assert_eq](#assert_eq)
+[assert_extends](#assert_extends)
+[assert_false](#assert_false)
+[assert_file_does_not_exist](#assert_file_does_not_exist)
+[assert_file_empty](#assert_file_empty)
+[assert_file_exists](#assert_file_exists)
+[assert_file_not_empty](#assert_file_not_empty)
+[assert_get_set_methods](#assert_get_set_methods)
+[assert_gt](#assert_gt)
+[assert_has_signal](#assert_has_signal)
+ +
+ +[assert_has](#assert_has)
+[assert_lt](#assert_lt)
+[assert_ne](#assert_ne)
+[assert_signal_emit_count](#assert_signal_emit_count)
+[assert_signal_emitted_with_parameters](#assert_signal_emitted_with_parameters)
+[assert_signal_emitted](#assert_signal_emitted)
+[assert_signal_not_emitted](#assert_signal_not_emitted)
+[assert_true](#assert_true)
+[get_signal_emit_count](#get_signal_emit_count)
+[get_signal_parameters](#get_signal_parameters)
+[watch_signals](#watch_signals)
+ +
# Table of Contents - 1. [Install](#install) - 1. [Gut Settings](#gut_settings) - 1. [Creating Tests](#creating_tests) - 1. [Method List](#method_list) - 1. [Asserting Things](#test_methods) - 1. [Methods for Configuring Test Execution](#gut_methods) - 1. [Extras](#extras) - 1. [Strict Type Checking](#strict) - 1. [File Manipulation](#files) - 1. [Watching Tests](#watch) - 1. [Output Detail](#output_detail) - 1. [Printing](#printing) - 1. [Advanced](#advanced) - 1. [Simulate](#simulate) - 1. [Yielding](#yielding) - 1. [Command Line Interface](#command_line) - 1. [Contributing](#contributing) +1. [Install](#install) +1. [Gut Settings](#gut_settings) +1. [Creating Tests](#creating_tests) +1. [Method List](#method_list) + 1. [Asserting Things](#test_methods) + 1. [Methods for Configuring Test Execution](#gut_methods) +1. [Extras](#extras) + 1. [Strict Type Checking](#strict) + 1. [File Manipulation](#files) + 1. [Watching Tests](#watch) + 1. [Output Detail](#output_detail) + 1. [Printing](#printing) +1. [Advanced](#advanced) + 1. [Simulate](#simulate) + 1. [Yielding](#yielding) +1. [Command Line Interface](#command_line) +1. [Contributing](#contributing) # Install ## New Installs and Upgrades -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). +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. @@ -126,7 +160,7 @@ flag a test as pending, the optional message is printed in the GUI pending('This test is not implemented yet') pending() ``` -#### assert_eq(got, expected, text="") +#### assert_eq(got, expected, text="") assert got == expected and prints optional text ``` python var one = 1 @@ -142,7 +176,7 @@ assert_eq(1, 2) # FAIL assert_eq('hello', 'world') # FAIL assert_eq(self, node1) # FAIL ``` -#### assert_ne(got, not_expected, text="") +#### assert_ne(got, not_expected, text="") asserts got != expected and prints optional text ``` python var two = 2 @@ -158,7 +192,7 @@ assert_ne(two, 2) # FAIL assert_ne('one', 'one') # FAIL assert_ne('2', 2) # FAIL ``` -#### assert_gt(got, expected, text="") +#### assert_gt(got, expected, text="") assserts got > expected ``` python var bigger = 5 @@ -175,7 +209,7 @@ assert_gt('a', 'a') # FAIL assert_gt(1.0, 1) # FAIL assert_gt(smaller, bigger) # FAIL ``` -#### assert_lt(got, expected, text="") +#### assert_lt(got, expected, text="") asserts got < expected ``` python var bigger = 5 @@ -188,7 +222,7 @@ gut.p('-- failing --') assert_lt('z', 'x') # FAIL assert_lt(-5, -5) # FAIL ``` -#### assert_true(got, text="") +#### assert_true(got, text="") asserts got == true ``` python gut.p('-- passing --') @@ -199,7 +233,7 @@ gut.p('-- failing --') assert_true(false) # FAIL assert_true('a' == 'b') # FAIL ``` -#### assert_false(got, text="") +#### assert_false(got, text="") asserts got == false ``` python gut.p('-- passing --') @@ -212,7 +246,7 @@ gut.p('-- failing --') assert_false(true) # FAIL assert_false('ABC' == 'ABC') # FAIL ``` -#### assert_between(got, expect_low, expect_high, text="") +#### assert_between(got, expect_low, expect_high, text="") asserts got > expect_low and <= expect_high ``` python gut.p('-- passing --') @@ -225,7 +259,7 @@ gut.p('-- failing --') assert_between('a', 'b', 'c') # FAIL assert_between(1, 5, 10) # FAIL ``` -#### assert_has(obj, element, text='') +#### assert_has(obj, element, text='') Asserts that the object passed in "has" the element. This works with any object that has a `has` method. ``` python var an_array = [1, 2, 3, 'four', 'five'] @@ -244,7 +278,7 @@ assert_has(an_array, self) # FAIL assert_has(a_hash, 3) # FAIL assert_has(a_hash, 'three') # FAIL ``` -#### assert_does_not_have(obj, element, text='') +#### assert_does_not_have(obj, element, text='') The inverse of `assert_has` ``` python var an_array = [1, 2, 3, 'four', 'five'] @@ -264,7 +298,7 @@ assert_does_not_have(a_hash, 'one') # FAIL assert_does_not_have(a_hash, '3') # FAIL ``` -#### assert_has_signal(object, signal_name) +#### assert_has_signal(object, signal_name) Asserts the passed in object has a signal with the specified name. It should be noted that all the asserts that verfy a signal was/wasn't emitted will first check that the object has the signal being asserted against. If it does not, a specific failure message will be given. This means you can usually skip the step of specifically verifying that the object has a signal and move on to making sure it emits the signal correctly. ``` python class SignalObject: @@ -288,12 +322,12 @@ func test_assert_has_signal(): assert_has_signal(Node2D.new(), 'exit_tree') ``` -#### watch_signals(object) +#### watch_signals(object) This must be called in order to make assertions based on signals being emitted. __Right now, this only supports signals that are emitted with 9 or less parameters. This can be extended but nine seemed like enough for now. The Godot documentation suggests that the limit is four but in my testing I found you can pass more.__ This must be called in each test in which you want to make signal based assertions in. You can call it multiple times with different objects. You should not call it multiple times with the same object in the same test. The objects that are watched are cleared after each test (specifically right before `teardown` is called). Under the covers, Gut will connect to all the signals an object has and it will track each time they fire. You can then use the following asserts and methods to verify things are acting correctl -#### assert_signal_emitted(object, signal_name) +#### assert_signal_emitted(object, signal_name) Assert that the specified object emitted the named signal. You must call `watch_signals` and pass it the object that you are making assertions about. This will fail if the object is not being watched or if the object does not have the specified signal. Since this will fail if the signal does not exist, you can often skip using `assert_has_signal`. ``` python class SignalObject: @@ -318,7 +352,7 @@ func test_assert_signal_emitted(): # Fails because the signal was not emitted assert_signal_emitted(obj, 'other_signal') ``` -#### assert_signal_not_emitted(object, signal_name) +#### assert_signal_not_emitted(object, signal_name) This works opposite of `assert_signal_emitted`. This will fail if the object is not being watched or if the object does not have the signal. ``` python class SignalObject: @@ -343,7 +377,7 @@ func test_assert_signal_not_emitted(): # Fails because the signal was emitted assert_signal_not_emitted(obj, 'some_signal') ``` -#### assert_signal_emitted_with_parameters(object, signal_name, parameters, index=-1) +#### assert_signal_emitted_with_parameters(object, signal_name, parameters, index=-1) Asserts that a signal was fired with the specified parameters. The expected parameters should be passed in as an array. An optional index can be passed when a signal has fired more than once. The default is to retrieve the most recent emission of the signal. This will fail with specific messages if the object is not being watched or the object does not have the specified signal @@ -380,7 +414,7 @@ func test_assert_signal_emitted_with_parameters(): # Fails because the parameters for the specified index do not match assert_signal_emitted_with_parameters(obj, 'some_signal', [1, 2, 3], 1) ``` -#### assert_signal_emit_count(object, signal_name) +#### assert_signal_emit_count(object, signal_name) Asserts that a signal fired a specific number of times. ``` python @@ -416,10 +450,10 @@ func test_assert_signal_emit_count(): assert_signal_emit_count(obj_a, 'some_signal', 0) assert_signal_emit_count(obj_b, 'other_signal', 283) ``` -#### get_signal_emit_count(object, signal_name) +#### get_signal_emit_count(object, signal_name) This will return the number of times a signal was fired. This gives you the freedom to make more complicated assertions if the spirit moves you. This will return -1 if the signal was not fired or the object was not being watched, or if the object does not have the signal. -#### get_signal_parameters(object, signal_name, index=-1) +#### get_signal_parameters(object, signal_name, index=-1) If you need to inspect the parameters in order to make more complicate assertions, then this will give you access to the parameters of any watched signal. This works the same way that `assert_signal_emitted_with_parameters` does. It takes an object, signal name, and an optional index. If the index is not specified then the parameters from the most recent emission will be returned. If the object is not being watched, the signal was not fired, or the object does not have the signal then `null` will be returned. ``` python class SignalObject: @@ -447,7 +481,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']) ``` -#### assert_file_exists(file_path) +#### assert_file_exists(file_path) asserts a file exists at the specified path ``` python func setup(): @@ -465,7 +499,7 @@ 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 ``` -#### assert_file_does_not_exist(file_path) +#### assert_file_does_not_exist(file_path) asserts a file does not exist at the specified path ``` python func setup(): @@ -482,7 +516,7 @@ func test_assert_file_does_not_exist(): gut.p('-- failing --') assert_file_does_not_exist('res://addons/gut/gut.gd') # FAIL ``` -#### assert_file_empty(file_path) +#### assert_file_empty(file_path) asserts the specified file is empty ``` python func setup(): @@ -498,7 +532,7 @@ func test_assert_file_empty(): gut.p('-- failing --') assert_file_empty('res://addons/gut/gut.gd') # FAIL ``` -#### assert_file_not_empty(file_path) +#### assert_file_not_empty(file_path) asserts the specified file is not empty ``` python func setup(): @@ -514,7 +548,31 @@ func test_assert_file_not_empty(): gut.p('-- failing --') assert_file_not_empty('user://some_test_file') # FAIL ``` -#### assert_get_set_methods(obj, property, default, set_to) +#### assert_extends(object, a_class, text) +Asserts that "object" extends "a_class". object must be an instance of an object. It cannot be any of the built in classes like Array or Int or Float. a_class must be a class, it can be loaded via load, a GDNative class such as Node or Label or anything else. + +``` python +func test_assert_extends(): + gut.p('-- passing --') + assert_extends(Node2D.new(), Node2D) + assert_extends(Label.new(), CanvasItem) + assert_extends(SubClass.new(), BaseClass) + # Since this is a test script that inherits from test.gd, so + # this passes. It's not obvious w/o seeing the whole script + # so I'm telling you. You'll just have to trust me. + assert_extends(self, load('res://addons/gut/test.gd')) + + var Gut = load('res://addons/gut/gut.gd') + var a_gut = Gut.new() + assert_extends(a_gut, Gut) + + gut.p('-- failing --') + assert_extends(Node2D.new(), Node2D.new()) + assert_extends(BaseClass.new(), SubClass) + assert_extends('a', 'b') + assert_extends([], Node) +``` +#### assert_get_set_methods(obj, property, default, set_to) I found that making tests for most getters and setters was repetitious and annoying. Enter `assert_get_set_methods`. This assertion handles 80% of your getter and setter testing needs. Given an object and a property name it will verify: * The object has a method called `get_` * The object has a method called `set_` @@ -522,7 +580,7 @@ I found that making tests for most getters and setters was repetitious and annoy * Once you set the property, the `get_`will return the value passed in. On the inside Gut actually performs up to 4 assertions. So if everything goes right you will have four passing asserts each time you call `assert_get_set_methods`. I say "up to 4 assertions" because there are 2 assertions to make sure the object has the methods and then 2 to verify they act correctly. If the object does not have the methods, it does not bother running the tests for the methods. -``` +``` python class SomeClass: var _count = 0 @@ -556,7 +614,7 @@ Print info to the GUI and console (if enabled). You can see examples if this in 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 class MovingNode: extends Node2D var _speed = 2 diff --git a/addons/gut/gut.gd b/addons/gut/gut.gd index e55d8166..575b50e8 100644 --- a/addons/gut/gut.gd +++ b/addons/gut/gut.gd @@ -5,7 +5,7 @@ #The MIT License (MIT) #===================== # -#Copyright (c) 2015 Tom "Butch" Wesley +#Copyright (c) 2017 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 @@ -28,9 +28,9 @@ ################################################################################ # View readme for usage details. # -# Version 4.0.0 +# Version 5.0.0 ################################################################################ -extends WindowDialog +extends "res://addons/gut/gut_gui.gd" # ########################### @@ -74,15 +74,16 @@ const LOG_LEVEL_ALL_ASSERTS = 2 const WAITING_MESSAGE = '/# waiting #/' const PAUSE_MESSAGE = '/# Pausing. Press continue button...#/' -var _is_running = false var _stop_pressed = false -var _signal_watcher = load('res://addons/gut/signal_watcher.gd').new() # Tests to run for the current script var _tests = [] # all the scripts that should be ran as test scripts var _test_scripts = [] +# The instanced scripts. This is populated as the scripts are run. +var _test_script_objects = [] + var _waiting = false var _done = false @@ -102,8 +103,6 @@ var _yield_between = { tests_since_last_yield = 0 } -var types = {} - var _set_yield_time_called = false # used when yielding to gut instead of some other # signal. Start with set_yield_time() @@ -111,273 +110,51 @@ var _yield_timer = Timer.new() var _runtime_timer = Timer.new() const RUNTIME_START_TIME = float(20000.0) -# various counters -var _summary = { - asserts = 0, - passed = 0, - failed = 0, - tests = 0, - scripts = 0, - pending = 0 -} - -#controls -var _ctrls = { - text_box = TextEdit.new(), - run_button = Button.new(), - copy_button = Button.new(), - clear_button = Button.new(), - continue_button = Button.new(), - log_level_slider = HSlider.new(), - scripts_drop_down = OptionButton.new(), - next_button = Button.new(), - previous_button = Button.new(), - stop_button = Button.new(), - script_progress = ProgressBar.new(), - test_progress = ProgressBar.new(), - runtime_label = Label.new(), - ignore_continue_checkbox = CheckBox.new(), - pass_count = Label.new(), - run_rest = Button.new() -} - -var _mouse_down = false -var _mouse_down_pos = null -var _mouse_in = false - var _unit_test_name = '' -var min_size = Vector2(650, 400) const SIGNAL_TESTS_FINISHED = 'tests_finished' -signal tests_finished const SIGNAL_STOP_YIELD_BEFORE_TEARDOWN = 'stop_yeild_before_teardown' -func _set_anchor_top_right(obj): - obj.set_anchor(MARGIN_RIGHT, ANCHOR_BEGIN) - obj.set_anchor(MARGIN_LEFT, ANCHOR_END) - obj.set_anchor(MARGIN_TOP, ANCHOR_BEGIN) - -func _set_anchor_bottom_right(obj): - obj.set_anchor(MARGIN_LEFT, ANCHOR_END) - obj.set_anchor(MARGIN_RIGHT, ANCHOR_END) - obj.set_anchor(MARGIN_TOP, ANCHOR_END) - obj.set_anchor(MARGIN_BOTTOM, ANCHOR_END) - -func _set_anchor_bottom_left(obj): - obj.set_anchor(MARGIN_LEFT, ANCHOR_BEGIN) - obj.set_anchor(MARGIN_TOP, ANCHOR_END) - obj.set_anchor(MARGIN_TOP, ANCHOR_END) - -func _init_types_dictionary(): - types[0] = 'TYPE_NIL' - types[1] = 'Bool' - types[2] = 'Int' - types[3] = 'Float/Real' - types[4] = 'String' - types[5] = 'Vector2' - types[6] = 'Rect2' - types[7] = 'Vector3' - types[8] = 'Matrix32' - types[9] = 'Plane' - types[10] = 'QUAT' - types[11] = 'AABB' - types[12] = 'Matrix3' - types[13] = 'Transform' - types[14] = 'Color' - types[15] = 'Image' - types[16] = 'Node Path' - types[17] = 'RID' - types[18] = 'Object' - types[19] = 'TYPE_INPUT_EVENT' - types[20] = 'Dictionary' - types[21] = 'Array' - types[22] = 'TYPE_RAW_ARRAY' - types[23] = 'TYPE_INT_ARRAY' - types[24] = 'TYPE_REAL_ARRAY' - types[25] = 'TYPE_STRING_ARRAY' - types[26] = 'TYPE_VECTOR2_ARRAY' - types[27] = 'TYPE_VECTOR3_ARRAY' - types[28] = 'TYPE_COLOR_ARRAY' - types[29] = 'TYPE_MAX' - -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- -func setup_controls(): - var button_size = Vector2(75, 35) - var button_spacing = Vector2(10, 0) - var pos = Vector2(0, 0) - - add_child(_ctrls.text_box) - _ctrls.text_box.set_size(Vector2(get_size().x - 4, 300)) - _ctrls.text_box.set_pos(Vector2(2, 0)) - _ctrls.text_box.set_readonly(true) - _ctrls.text_box.set_syntax_coloring(true) - _ctrls.text_box.set_anchor(MARGIN_LEFT, ANCHOR_BEGIN) - _ctrls.text_box.set_anchor(MARGIN_RIGHT, ANCHOR_END) - _ctrls.text_box.set_anchor(MARGIN_TOP, ANCHOR_BEGIN) - _ctrls.text_box.set_anchor(MARGIN_BOTTOM, ANCHOR_END) - - add_child(_ctrls.copy_button) - _ctrls.copy_button.set_text("Copy") - _ctrls.copy_button.set_size(button_size) - _ctrls.copy_button.set_pos(Vector2(get_size().x - 5 - button_size.x, _ctrls.text_box.get_size().y + 10)) - _ctrls.copy_button.connect("pressed", self, "_copy_button_pressed") - _set_anchor_bottom_right(_ctrls.copy_button) +# 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 - add_child(_ctrls.clear_button) - _ctrls.clear_button.set_text("Clear") - _ctrls.clear_button.set_size(button_size) - _ctrls.clear_button.set_pos(_ctrls.copy_button.get_pos() - Vector2(button_size.x, 0) - button_spacing) +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +func _init(): + add_user_signal(SIGNAL_TESTS_FINISHED) + add_user_signal(SIGNAL_STOP_YIELD_BEFORE_TEARDOWN) + add_user_signal('timeout') + +# ------------------------------------------------------------------------------ +# Connect all the controls created in the parent class to the methods here. +# ------------------------------------------------------------------------------ +func _connect_controls(): + _ctrls.copy_button.connect("pressed", self, "_copy_button_pressed") _ctrls.clear_button.connect("pressed", self, "clear_text") - _set_anchor_bottom_right(_ctrls.clear_button) - - add_child(_ctrls.pass_count) - _ctrls.pass_count.set_text('0 - 0') - _ctrls.pass_count.set_size(Vector2(100, 30)) - _ctrls.pass_count.set_pos(Vector2(550, 0)) - _ctrls.pass_count.set_align(HALIGN_RIGHT) - _set_anchor_top_right(_ctrls.pass_count) - - add_child(_ctrls.continue_button) - _ctrls.continue_button.set_text("Continue") - _ctrls.continue_button.set_size(Vector2(100, 25)) - _ctrls.continue_button.set_pos(Vector2(_ctrls.clear_button.get_pos().x, _ctrls.clear_button.get_pos().y + _ctrls.clear_button.get_size().y + 10)) - _ctrls.continue_button.set_disabled(true) _ctrls.continue_button.connect("pressed", self, "_on_continue_button_pressed") - _set_anchor_bottom_right(_ctrls.continue_button) - - 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.connect('pressed', self, '_on_ignore_continue_checkbox_pressed') - _ctrls.ignore_continue_checkbox.set_size(Vector2(50, 30)) - _ctrls.ignore_continue_checkbox.set_pos(Vector2(_ctrls.continue_button.get_pos().x, _ctrls.continue_button.get_pos().y + _ctrls.continue_button.get_size().y - 5)) - _set_anchor_bottom_right(_ctrls.ignore_continue_checkbox) - - var log_label = Label.new() - add_child(log_label) - log_label.set_text("Log Level") - log_label.set_pos(Vector2(10, _ctrls.text_box.get_size().y + 1)) - _set_anchor_bottom_left(log_label) - - add_child(_ctrls.log_level_slider) - _ctrls.log_level_slider.set_size(Vector2(75, 30)) - _ctrls.log_level_slider.set_pos(Vector2(10, log_label.get_pos().y + 20)) - _ctrls.log_level_slider.set_min(0) - _ctrls.log_level_slider.set_max(2) - _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.connect("value_changed", self, "_on_log_level_slider_changed") - _ctrls.log_level_slider.set_value(_log_level) - _set_anchor_bottom_left(_ctrls.log_level_slider) - - var script_prog_label = Label.new() - add_child(script_prog_label) - script_prog_label.set_pos(Vector2(100, log_label.get_pos().y)) - script_prog_label.set_text('Scripts:') - _set_anchor_bottom_left(script_prog_label) - - add_child(_ctrls.script_progress) - _ctrls.script_progress.set_size(Vector2(200, 10)) - _ctrls.script_progress.set_pos(script_prog_label.get_pos() + Vector2(70, 0)) - _ctrls.script_progress.set_min(0) - _ctrls.script_progress.set_max(1) - _ctrls.script_progress.set_unit_value(1) - _set_anchor_bottom_left(_ctrls.script_progress) - - var test_prog_label = Label.new() - add_child(test_prog_label) - test_prog_label.set_pos(Vector2(100, log_label.get_pos().y + 15)) - test_prog_label.set_text('Tests:') - _set_anchor_bottom_left(test_prog_label) - - add_child(_ctrls.test_progress) - _ctrls.test_progress.set_size(Vector2(200, 10)) - _ctrls.test_progress.set_pos(test_prog_label.get_pos() + Vector2(70, 0)) - _ctrls.test_progress.set_min(0) - _ctrls.test_progress.set_max(1) - _ctrls.test_progress.set_unit_value(1) - _set_anchor_bottom_left(_ctrls.test_progress) - - add_child(_ctrls.previous_button) - _ctrls.previous_button.set_size(Vector2(50, 25)) - pos = _ctrls.test_progress.get_pos() + Vector2(250, 25) - pos.x -= 300 - _ctrls.previous_button.set_pos(pos) - _ctrls.previous_button.set_text("<") - _ctrls.previous_button.connect("pressed", self, '_on_previous_button_pressed') - _set_anchor_bottom_left(_ctrls.previous_button) - - add_child(_ctrls.stop_button) - _ctrls.stop_button.set_size(Vector2(50, 25)) - pos.x += 60 - _ctrls.stop_button.set_pos(pos) - _ctrls.stop_button.set_text('stop') _ctrls.stop_button.connect("pressed", self, '_on_stop_button_pressed') - _set_anchor_bottom_left(_ctrls.stop_button) - - add_child(_ctrls.run_rest) - _ctrls.run_rest.set_text('run') - _ctrls.run_rest.set_size(Vector2(50, 25)) - pos.x += 60 - _ctrls.run_rest.set_pos(pos) _ctrls.run_rest.connect('pressed', self, '_on_run_rest_pressed') - _set_anchor_bottom_left(_ctrls.run_rest) - - add_child(_ctrls.next_button) - _ctrls.next_button.set_size(Vector2(50, 25)) - pos.x += 60 - _ctrls.next_button.set_pos(pos) - _ctrls.next_button.set_text(">") + _ctrls.previous_button.connect("pressed", self, '_on_previous_button_pressed') _ctrls.next_button.connect("pressed", self, '_on_next_button_pressed') - _set_anchor_bottom_left(_ctrls.next_button) - - add_child(_ctrls.runtime_label) - _ctrls.runtime_label.set_text('0.0') - _ctrls.runtime_label.set_size(Vector2(50, 30)) - _ctrls.runtime_label.set_pos(Vector2(_ctrls.clear_button.get_pos().x - 90, _ctrls.next_button.get_pos().y)) - _set_anchor_bottom_right(_ctrls.runtime_label) - - # the drop down has to be one of the last added so that when then list of - # scripts is displayed, other controls do not get in the way of selecting - # an item in the list. - add_child(_ctrls.scripts_drop_down) - _ctrls.scripts_drop_down.set_size(Vector2(375, 25)) - _ctrls.scripts_drop_down.set_pos(Vector2(10, _ctrls.log_level_slider.get_pos().y + 50)) - _set_anchor_bottom_left(_ctrls.scripts_drop_down) _ctrls.scripts_drop_down.connect('item_selected', self, '_on_script_selected') - _ctrls.scripts_drop_down.set_clip_text(true) - - add_child(_ctrls.run_button) - _ctrls.run_button.set_text('<- run') - _ctrls.run_button.set_size(Vector2(50, 25)) - _ctrls.run_button.set_pos(_ctrls.scripts_drop_down.get_pos() + Vector2(_ctrls.scripts_drop_down.get_size().x + 5, 0)) _ctrls.run_button.connect("pressed", self, "_on_run_button_pressed") - _set_anchor_bottom_left(_ctrls.run_button) - -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- -func _init(): - add_user_signal(SIGNAL_TESTS_FINISHED) - add_user_signal(SIGNAL_STOP_YIELD_BEFORE_TEARDOWN) - add_user_signal('timeout') - _init_types_dictionary() - -#------------------------------------------------------------------------------- -#Initialize controls -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Initialize controls +# ------------------------------------------------------------------------------ func _ready(): + set_it_up() set_process_input(true) - - show() + _connect_controls() set_pos(get_pos() + Vector2(0, 20)) - self.set_size(min_size) - - setup_controls() add_child(_wait_timer) _wait_timer.set_wait_time(1) @@ -397,11 +174,6 @@ func _ready(): _runtime_timer.set_one_shot(true) _runtime_timer.set_wait_time(RUNTIME_START_TIME) - self.connect("mouse_enter", self, "_on_mouse_enter") - self.connect("mouse_exit", self, "_on_mouse_exit") - set_process(true) - - set_pause_mode(PAUSE_MODE_PROCESS) add_directory(_directory1) add_directory(_directory2) add_directory(_directory3) @@ -409,8 +181,6 @@ func _ready(): add_directory(_directory5) add_directory(_directory6) - _update_controls() - if(_select_script != null): select_script(_select_script) @@ -419,67 +189,7 @@ func _ready(): if(_run_on_load): test_scripts(_select_script == null) - -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- -func _process(delta): - if(_is_running): - _ctrls.runtime_label.set_text(str(RUNTIME_START_TIME - _runtime_timer.get_time_left()).pad_decimals(3) + ' s') - -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- -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.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_pos().x - 10 and event.pos.y > get_size().y + get_pos().y - 10): - if event.pressed: - _mouse_down = true - _mouse_down_pos = event.pos - else: - _mouse_down = false - #Reszie - if(event.type == InputEvent.MOUSE_MOTION): - 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 - - if(new_size.x < min_size.x): - new_size.x = min_size.x - new_mouse_down_pos.x = _mouse_down_pos.x - - if(new_size.y < min_size.y): - new_size.y = min_size.y - new_mouse_down_pos.y = _mouse_down_pos.y - - _mouse_down_pos = new_mouse_down_pos - set_size(new_size) - -#------------------------------------------------------------------------------- -#Custom drawing to indicate results. -#------------------------------------------------------------------------------- -func _draw(): - #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)) + show() ##################### # @@ -487,69 +197,55 @@ func _draw(): # ##################### -#------------------------------------------------------------------------------- -#Timeout for the built in timer. emits the timeout signal. Start timer -#with set_yield_time() -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +func _process(delta): + if(_is_running): + _ctrls.runtime_label.set_text(str(RUNTIME_START_TIME - _runtime_timer.get_time_left()).pad_decimals(3) + ' s') + +# ------------------------------------------------------------------------------ +# Timeout for the built in timer. emits the timeout signal. Start timer +# with set_yield_time() +# ------------------------------------------------------------------------------ func _on_yield_timer_timeout(): emit_signal('timeout') -#------------------------------------------------------------------------------- -#detect mouse movement -#------------------------------------------------------------------------------- -func _on_mouse_enter(): - _mouse_in = true - -#------------------------------------------------------------------------------- -#detect mouse movement -#------------------------------------------------------------------------------- -func _on_mouse_exit(): - _mouse_in = false - _mouse_down = false - -#------------------------------------------------------------------------------- -#Run either the selected test or all tests. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Run either the selected test or all tests. +# ------------------------------------------------------------------------------ func _on_run_button_pressed(): test_scripts() -#------------------------------------------------------------------------------- -#Send text box text to clipboard -#------------------------------------------------------------------------------- -func _copy_button_pressed(): - _ctrls.text_box.select_all() - _ctrls.text_box.copy() - -#------------------------------------------------------------------------------- -#Continue processing after pause. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Continue processing after pause. +# ------------------------------------------------------------------------------ func _on_continue_button_pressed(): _pause_before_teardown = false _ctrls.continue_button.set_disabled(true) emit_signal(SIGNAL_STOP_YIELD_BEFORE_TEARDOWN) -#------------------------------------------------------------------------------- -#Change the log level. Will be visible the next time tests are run. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Change the log level. Will be visible the next time tests are run. +# ------------------------------------------------------------------------------ func _on_log_level_slider_changed(value): _log_level = _ctrls.log_level_slider.get_value() -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ func _on_previous_button_pressed(): if(_ctrls.scripts_drop_down.get_selected() > 0): _ctrls.scripts_drop_down.select(_ctrls.scripts_drop_down.get_selected() -1) _update_controls() -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ func _on_next_button_pressed(): if(_ctrls.scripts_drop_down.get_selected() < _ctrls.scripts_drop_down.get_item_count() -1): _ctrls.scripts_drop_down.select(_ctrls.scripts_drop_down.get_selected() +1) _update_controls() -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ func _on_stop_button_pressed(): _stop_pressed = true _ctrls.stop_button.set_disabled(true) @@ -559,8 +255,8 @@ func _on_stop_button_pressed(): else: _waiting = false -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ func _on_ignore_continue_checkbox_pressed(): _ignore_pause_before_teardown = _ctrls.ignore_continue_checkbox.is_pressed() # If you want to ignore them, then you probably just want to continue @@ -568,13 +264,13 @@ func _on_ignore_continue_checkbox_pressed(): if(!_ctrls.continue_button.is_disabled()): _on_continue_button_pressed() -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ func _on_script_selected(id): _update_controls() -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ func _on_run_rest_pressed(): test_scripts(true) @@ -583,34 +279,11 @@ func _on_run_rest_pressed(): # Private # ##################### -#------------------------------------------------------------------------------- -# Updates the display -#------------------------------------------------------------------------------- -func _update_controls(): - if(_is_running): - _ctrls.previous_button.set_disabled(true) - _ctrls.next_button.set_disabled(true) - _ctrls.pass_count.show() - else: - _ctrls.previous_button.set_disabled(_ctrls.scripts_drop_down.get_selected() == 0) - _ctrls.next_button.set_disabled(_ctrls.scripts_drop_down.get_selected() == _ctrls.scripts_drop_down.get_item_count() -1) - _ctrls.pass_count.hide() - - # disabled during run - _ctrls.run_button.set_disabled(_is_running) - _ctrls.run_rest.set_disabled(_is_running) - _ctrls.scripts_drop_down.set_disabled(_is_running) - # enabled during run - _ctrls.stop_button.set_disabled(!_is_running) - _ctrls.pass_count.set_text(str( _summary.passed, ' - ', _summary.failed)) - - - -#------------------------------------------------------------------------------- -#Parses out the tests based on the _test_prefix. Fills the _tests array with -#instances of OneTest. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Parses out the tests based on the _test_prefix. Fills the _tests array with +# instances of OneTest. +# ------------------------------------------------------------------------------ func _parse_tests(script): var file = File.new() var line = "" @@ -631,45 +304,23 @@ func _parse_tests(script): file.close() -#------------------------------------------------------------------------------- -#Fail an assertion. Causes test and script to fail as well. -#------------------------------------------------------------------------------- -func _fail(text): - _summary.asserts += 1 - _summary.failed += 1 - if(_current_test != null): - _current_test.passed = false - p('FAILED: ' + text, LOG_LEVEL_FAIL_ONLY) - if(_current_test != null): - p(' at line ' + str(_current_test.line_number), LOG_LEVEL_FAIL_ONLY) - _update_controls() - end_yielded_test() - - -#------------------------------------------------------------------------------- -#Pass an assertion. -#------------------------------------------------------------------------------- -func _pass(text): - _summary.asserts += 1 - _summary.passed += 1 - if(_log_level >= LOG_LEVEL_ALL_ASSERTS): - p("PASSED: " + text, LOG_LEVEL_ALL_ASSERTS) - _update_controls() - end_yielded_test() - -#------------------------------------------------------------------------------- -#Convert the _summary struct into text for display -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Convert the _summary dictionary into text +# ------------------------------------------------------------------------------ func _get_summary_text(): - var to_return = "/*****************\nSummary\n*****************/\n" - to_return += str(_summary.scripts) + " Scripts\n" - to_return += str(_summary.tests) + " Tests\n" - to_return += str(_summary.asserts) + " Asserts\n" - to_return += str(_summary.passed) + " Passed\n" - to_return += str(_summary.pending) + " Pending\n" - to_return += str(_summary.failed) + " Failed\n" + 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" + if(_summary.tests > 0): to_return += '+++ ' + str(_summary.passed) + ' passed ' + str(_summary.failed) + ' failed. ' + \ "Tests finished in: " + _ctrls.runtime_label.get_text() + ' +++' @@ -680,47 +331,75 @@ func _get_summary_text(): 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 +------------- +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 +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 -#------------------------------------------------------------------------------- -#Initialize variables for each run of a single test script. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# 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 + _summary.tally_passed = 0 + _summary.tally_failed = 0 _log_text = "" - _ctrls.text_box.clear_colors() - _ctrls.text_box.add_keyword_color("PASSED", Color(0, 1, 0)) - _ctrls.text_box.add_keyword_color("FAILED", Color(1, 0, 0)) - _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') _current_test = null _is_running = true _update_controls() - _ctrls.script_progress.set_max(_test_scripts.size()) - _ctrls.script_progress.set_value(0) - _ctrls.test_progress.set_max(1) _runtime_timer.start() _yield_between.tests_since_last_yield = 0 + ._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. +# ------------------------------------------------------------------------------ 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) + _runtime_timer.stop() _is_running = false update() @@ -728,16 +407,19 @@ func _end_run(): emit_signal(SIGNAL_TESTS_FINISHED) set_title("Finished. " + str(get_fail_count()) + " failures.") -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Checks the passed in thing to see if it is a "function state" object that gets +# returned when a function yields. +# ------------------------------------------------------------------------------ func _is_function_state(script_result): return script_result != null and \ typeof(script_result) == TYPE_OBJECT and \ script_result.get_type() == 'GDFunctionState' -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Print out the heading for a new script +# ------------------------------------------------------------------------------ func _print_script_heading(script_name): p("/-----------------------------------------") p("Testing Script " + script_name, 0) @@ -745,8 +427,10 @@ func _print_script_heading(script_name): p(' Only running tests like: "' + _unit_test_name + '"') p("-----------------------------------------/") -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Initialize a new test script object. The file is loaded from the passed in path +# and the tests are parsed out. +# ------------------------------------------------------------------------------ func _init_test_script(script_path): _parse_tests(script_path) var test_script = load(script_path).new() @@ -755,10 +439,10 @@ func _init_test_script(script_path): return test_script -#------------------------------------------------------------------------------- -# just gets more logic out of _test_the_scripts. Decides if we should yield after +# ------------------------------------------------------------------------------ +# Just gets more logic out of _test_the_scripts. Decides if we should yield after # this test based on flags and counters. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func _should_yield_now(): var should = _yield_between.should and \ _yield_between.tests_since_last_yield == _yield_between.after_x_tests @@ -768,12 +452,12 @@ func _should_yield_now(): _yield_between.tests_since_last_yield += 1 return should -#------------------------------------------------------------------------------- -#Run all tests in a script. This is the core logic for running tests. +# ------------------------------------------------------------------------------ +# Run all tests in a script. This is the core logic for running tests. # -#Note, this has to stay as a giant monstrosity of a method because of the -#yields. -#------------------------------------------------------------------------------- +# Note, this has to stay as a giant monstrosity of a method because of the +# yields. +# ------------------------------------------------------------------------------ func _test_the_scripts(): _init_run() var file = File.new() @@ -787,6 +471,7 @@ func _test_the_scripts(): p("FAILED COULD NOT FIND FILE: " + _test_scripts[s]) else: var test_script = _init_test_script(_test_scripts[s]) + _test_script_objects.append(test_script) var script_result = null _summary.scripts += 1 @@ -834,8 +519,9 @@ func _test_the_scripts(): _ctrls.continue_button.set_disabled(false) yield(self, SIGNAL_STOP_YIELD_BEFORE_TEARDOWN) - _signal_watcher.clear() + test_script.clear_signal_watcher() test_script.teardown() + if(_current_test.passed): _ctrls.text_box.add_keyword_color(_current_test.name, Color(0, 1, 0)) else: @@ -851,7 +537,11 @@ func _test_the_scripts(): _ctrls.test_progress.set_value(i + 1) test_script.postrun_teardown() - test_script.free() + # This might end up being very resource intensive if the scripts + # don't clean up after themselves. Might have to consolidate output + # into some other structure and kill the script objects with + # test_script.free() instead of remove child. + remove_child(test_script) #END TESTS IN SCRIPT LOOP _current_test = null @@ -862,18 +552,30 @@ func _test_the_scripts(): _end_run() + +func _pass(): + _summary.tally_passed += 1 + _update_controls() + +func _fail(): + _summary.tally_failed += 1 + if(_current_test != null): + _current_test.passed = false + p(' at line ' + str(_current_test.line_number), LOG_LEVEL_FAIL_ONLY) + _update_controls() + ######################### # # public # ######################### -#------------------------------------------------------------------------------- -#Conditionally prints the text to the console/results variable based on the -#current log level and what level is passed in. Whenever currently in a test, -#the text will be indented under the test. It can be further indented if -#desired. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Conditionally prints the text to the console/results variable based on the +# current log level and what level is passed in. Whenever currently in a test, +# the text will be indented under the test. It can be further indented if +# desired. +# ------------------------------------------------------------------------------ func p(text, level=0, indent=0): var str_text = str(text) var to_print = "" @@ -916,9 +618,9 @@ func p(text, level=0, indent=0): # ################ -#------------------------------------------------------------------------------- -#Runs all the scripts that were added using add_script -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Runs all the scripts that were added using add_script +# ------------------------------------------------------------------------------ func test_scripts(run_rest=false): clear_text() _test_scripts.clear() @@ -932,18 +634,18 @@ func test_scripts(run_rest=false): _test_the_scripts() -#------------------------------------------------------------------------------- -#Runs a single script passed in. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Runs a single script passed in. +# ------------------------------------------------------------------------------ func test_script(script): _test_scripts.clear() _test_scripts.append(script) _test_the_scripts() _test_scripts.clear() -#------------------------------------------------------------------------------- -#Adds a script to be run when test_scripts called -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Adds a script to be run when test_scripts called +# ------------------------------------------------------------------------------ func add_script(script, select_this_one=false): if(_test_scripts.has(script)): return @@ -953,16 +655,16 @@ func add_script(script, select_this_one=false): # Move the run_button in case the size of the path of the script caused the # drop down to resize. _ctrls.run_button.set_pos(_ctrls.scripts_drop_down.get_pos() + \ - Vector2(_ctrls.scripts_drop_down.get_size().x + 5, 0)) + Vector2(_ctrls.scripts_drop_down.get_size().x + 5, 0)) if(select_this_one): _ctrls.scripts_drop_down.select(_ctrls.scripts_drop_down.get_item_count() -1) -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # Add all scripts in the specified directory that start with the prefix and end # with the suffix. Does not look in sub directories. Can be called multiple # times. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func add_directory(path, prefix=_file_prefix, suffix=_file_extension): var d = Directory.new() if(!d.dir_exists(path)): @@ -970,9 +672,9 @@ func add_directory(path, prefix=_file_prefix, suffix=_file_extension): d.open(path) d.list_dir_begin() - #Traversing a directory is kinda odd. You have to start the process of listing - #the contents of a directory with list_dir_begin then use get_next until it - #returns an empty string. Then I guess you should end it. + # Traversing a directory is kinda odd. You have to start the process of listing + # the contents of a directory with list_dir_begin then use get_next until it + # returns an empty string. Then I guess you should end it. var thing = d.get_next() var full_path = '' while(thing != ''): @@ -984,14 +686,14 @@ func add_directory(path, prefix=_file_prefix, suffix=_file_extension): thing = d.get_next() d.list_dir_end() -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # This will try to find a script in the list of scripts to test that contains # the specified script name. It does not have to be a full match. It will # select the first matching occurance so that this script will run when run_tests # is called. Works the same as the select_this_one option of add_script. # # returns whether it found a match or not -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func select_script(script_name): var found = false var idx = 0 @@ -1004,323 +706,7 @@ func select_script(script_name): idx += 1 return found -################ -# -# ASSERTS -# -################ -func _pass_if_datatypes_match(got, expected, text): - var passed = true - - if(!_disable_strict_datatype_checks): - var got_type = typeof(got) - var expect_type = typeof(expected) - if(got_type != expect_type and got != null and expected != null): - # If we have a mismatch between float and int (types 2 and 3) then - # print out a warning but do not fail. - if([2, 3].has(got_type) and [2, 3].has(expect_type)): - p(str('Warn: Float/Int comparison. Got ', types[got_type], ' but expected ', types[expect_type]), 1) - else: - _fail('Cannot compare ' + types[got_type] + '[' + str(got) + '] to ' + types[expect_type] + '[' + str(expected) + ']. ' + text) - passed = false - - return passed - -#------------------------------------------------------------------------------- -#Asserts that the expected value equals the value got. -#------------------------------------------------------------------------------- -func assert_eq(got, expected, text=""): - var disp = "[" + str(got) + "] expected to equal [" + str(expected) + "]: " + text - if(_pass_if_datatypes_match(got, expected, text)): - if(expected != got): - _fail(disp) - else: - _pass(disp) - -#------------------------------------------------------------------------------- -#Asserts that the value got does not equal the "not expected" value. -#------------------------------------------------------------------------------- -func assert_ne(got, not_expected, text=""): - var disp = "[" + str(got) + "] expected to be anything except [" + str(not_expected) + "]: " + text - if(_pass_if_datatypes_match(got, not_expected, text)): - if(got == not_expected): - _fail(disp) - else: - _pass(disp) -#------------------------------------------------------------------------------- -#Asserts got is greater than expected -#------------------------------------------------------------------------------- -func assert_gt(got, expected, text=""): - var disp = "[" + str(got) + "] expected to be > than [" + str(expected) + "]: " + text - if(_pass_if_datatypes_match(got, expected, text)): - if(got > expected): - _pass(disp) - else: - _fail(disp) - -#------------------------------------------------------------------------------- -#Asserts got is less than expected -#------------------------------------------------------------------------------- -func assert_lt(got, expected, text=""): - var disp = "[" + str(got) + "] expected to be < than [" + str(expected) + "]: " + text - if(_pass_if_datatypes_match(got, expected, text)): - if(got < expected): - _pass(disp) - else: - _fail(disp) - -#------------------------------------------------------------------------------- -#asserts that got is true -#------------------------------------------------------------------------------- -func assert_true(got, text=""): - if(!got): - _fail(text) - else: - _pass(text) - -#------------------------------------------------------------------------------- -#Asserts that got is false -#------------------------------------------------------------------------------- -func assert_false(got, text=""): - if(got): - _fail(text) - else: - _pass(text) - -#------------------------------------------------------------------------------- -#Asserts value is between (inclusive) the two expected values. -#------------------------------------------------------------------------------- -func assert_between(got, expect_low, expect_high, text=""): - var disp = "[" + str(got) + "] expected to be between [" + str(expect_low) + "] and [" + str(expect_high) + "]: " + text - - if(_pass_if_datatypes_match(got, expect_low, text) and _pass_if_datatypes_match(got, expect_high, text)): - if(expect_low > expect_high): - disp = "INVALID range. [" + str(expect_low) + "] is not less than [" + str(expect_high) + "]" - _fail(disp) - else: - if(got < expect_low or got > expect_high): - _fail(disp) - else: - _pass(disp) - -#------------------------------------------------------------------------------- -# Uses the 'has' method of the object passed in to determine if it contains -# the passed in element. -#------------------------------------------------------------------------------- -func assert_has(obj, element, text=""): - var disp = str('Expected [', obj, '] to contain value: [', element, ']: ', text) - if(obj.has(element)): - _pass(disp) - else: - _fail(disp) - -func assert_does_not_have(obj, element, text=""): - var disp = str('Expected [', obj, '] to NOT contain value: [', element, ']: ', text) - if(obj.has(element)): - _fail(disp) - else: - _pass(disp) -#------------------------------------------------------------------------------- -#Asserts that a file exists -#------------------------------------------------------------------------------- -func assert_file_exists(file_path): - var disp = 'expected [' + file_path + '] to exist.' - var f = File.new() - if(f.file_exists(file_path)): - _pass(disp) - else: - _fail(disp) - -#------------------------------------------------------------------------------- -#Asserts that a file should not exist -#------------------------------------------------------------------------------- -func assert_file_does_not_exist(file_path): - var disp = 'expected [' + file_path + '] to NOT exist' - var f = File.new() - if(!f.file_exists(file_path)): - _pass(disp) - else: - _fail(disp) - -#------------------------------------------------------------------------------- -# Asserts the specified file is empty -#------------------------------------------------------------------------------- -func assert_file_empty(file_path): - var disp = 'expected [' + file_path + '] to be empty' - var f = File.new() - if(f.file_exists(file_path) and is_file_empty(file_path)): - _pass(disp) - else: - _fail(disp) - -#------------------------------------------------------------------------------- -#------------------------------------------------------------------------------- -func assert_file_not_empty(file_path): - var disp = 'expected [' + file_path + '] to contain data' - if(!is_file_empty(file_path)): - _pass(disp) - else: - _fail(disp) - -#------------------------------------------------------------------------------- -# Verifies the object has get and set methods for the property passed in. The -# property isn't tied to anything, just a name to be appended to the end of -# get_ and set_. Asserts the get_ and set_ methods exist, if not, it stops there. -# If they exist then it asserts get_ returns the expected default then calls -# set_ and asserts get_ has the value it was set to. -#------------------------------------------------------------------------------- -func assert_get_set_methods(obj, property, default, set_to): - var fail_count = get_fail_count() - var get = 'get_' + property - var set = 'set_' + property - assert_true(obj.has_method(get), 'Should have get method: ' + get) - assert_true(obj.has_method(set), 'Should have set method: ' + set) - if(get_fail_count() > fail_count): - return - assert_eq(obj.call(get), default, 'It should have the expected default value.') - obj.call(set, set_to) - assert_eq(obj.call(get), set_to, 'The set value should have been returned.') - - -#------------------------------------------------------------------------------- -# Signal assertion helper. Do not call directly, use _can_make_signal_assertions -#------------------------------------------------------------------------------- -func _fail_if_does_not_have_signal(object, signal_name): - var did_fail = false - if(!object.has_user_signal(signal_name)): - _fail(str('Object ', object, ' does not have the signal [', signal_name, ']')) - did_fail = true - return did_fail -#------------------------------------------------------------------------------- -# Signal assertion helper. Do not call directly, use _can_make_signal_assertions -#------------------------------------------------------------------------------- -func _fail_if_not_watching(object): - var did_fail = false - if(!_signal_watcher.is_watching_object(object)): - _fail(str('Cannot make signal assertions because the object ', object, \ - ' is not being watched. Call watch_signals(some_object) to be able to make assertions about signals.')) - did_fail = true - return did_fail - -#------------------------------------------------------------------------------- -# Signal assertion helper. -# -# Verifies that the object and signal are valid for making signal assertions. -# This will fail with specific messages that indicate why they are not valid. -# This returns true/false to indicate if the object and signal are valid. -#------------------------------------------------------------------------------- -func _can_make_signal_assertions(object, signal_name): - return !(_fail_if_not_watching(object) or _fail_if_does_not_have_signal(object, signal_name)) - -#------------------------------------------------------------------------------- -# Watch the signals for an object. This must be called before you can make -# any assertions about the signals themselves. -#------------------------------------------------------------------------------- -func watch_signals(object): - _signal_watcher.watch_signals(object) - -#------------------------------------------------------------------------------- -# Asserts that a signal has been emitted at least once. -# -# This will fail with specific messages if the object is not being watched or -# 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) - if(_can_make_signal_assertions(object, signal_name)): - if(_signal_watcher.did_emit(object, signal_name)): - _pass(disp) - else: - _fail(disp) - -#------------------------------------------------------------------------------- -# Asserts that a signal has not been emitted. -# -# This will fail with specific messages if the object is not being watched or -# the object does not have the specified signal -#------------------------------------------------------------------------------- -func assert_signal_not_emitted(object, signal_name, text=""): - var disp = str('Expected object ', object, ' to NOT emit signal [', signal_name, ']: ', text) - if(_can_make_signal_assertions(object, signal_name)): - if(_signal_watcher.did_emit(object, signal_name)): - _fail(disp) - else: - _pass(disp) - -#------------------------------------------------------------------------------- -# Asserts that a signal was fired with the specified parameters. The expected -# parameters should be passed in as an array. An optional index can be passed -# when a signal has fired more than once. The default is to retrieve the most -# recent emission of the signal. -# -# This will fail with specific messages if the object is not being watched or -# the object does not have the specified signal -#------------------------------------------------------------------------------- -func assert_signal_emitted_with_parameters(object, signal_name, parameters, index=-1): - var disp = str('Expected object ', object, ' to emit signal [', signal_name, '] with parameters ', parameters, ', got ') - if(_can_make_signal_assertions(object, signal_name)): - if(_signal_watcher.did_emit(object, signal_name)): - var parms_got = _signal_watcher.get_signal_parameters(object, signal_name, index) - if(parameters == parms_got): - _pass(str(disp, parms_got)) - else: - _fail(str(disp, parms_got)) - else: - _fail(str('Object ', object, ' did not emit signal [', signal_name, ']')) -#------------------------------------------------------------------------------- -# Assert that a signal has been emitted a specific number of times. -# -# This will fail with specific messages if the object is not being watched or -# the object does not have the specified signal -#------------------------------------------------------------------------------- -func assert_signal_emit_count(object, signal_name, times, text=""): - - if(_can_make_signal_assertions(object, signal_name)): - var count = _signal_watcher.get_emit_count(object, signal_name) - var disp = str('Expected the signal [', signal_name, '] emit count of [', count, '] to equal [', times, ']: ', text) - if(count== times): - _pass(disp) - else: - _fail(disp) - -#------------------------------------------------------------------------------- -# Assert that the passed in object has the specfied signal -#------------------------------------------------------------------------------- -func assert_has_signal(object, signal_name, text=""): - var disp = str('Expected object ', object, ' to have signal [', signal_name, ']: ', text) - if(object.has_user_signal(signal_name)): - _pass(disp) - else: - _fail(disp) - -#------------------------------------------------------------------------------- -# Returns the number of times a signal was emitted. -1 returned if the object -# is not being watched. -#------------------------------------------------------------------------------- -func get_signal_emit_count(object, signal_name): - return _signal_watcher.get_emit_count(object, signal_name) - -#------------------------------------------------------------------------------- -# Get the parmaters of a fired signal. If the signal was not fired null is -# returned. You can specify an optional index (use get_signal_emit_count to -# determine the number of times it was emitted). The default index is the -# latest time the signal was fired (size() -1 insetead of 0). The parameters -# returned are in an array. -#------------------------------------------------------------------------------- -func get_signal_parameters(object, signal_name, index=-1): - return _signal_watcher.get_signal_parameters(object, signal_name, index) - -#------------------------------------------------------------------------------- -# Mark the current test as pending. -#------------------------------------------------------------------------------- -func pending(text=""): - _summary.pending += 1 - if(text == ""): - p("Pending") - else: - p("Pending: " + text) - end_yielded_test() ################ # # MISC @@ -1332,95 +718,94 @@ func disable_strict_datatype_checks(should): func is_strict_datatype_checks_disabled(): return _disable_strict_datatype_checks -#------------------------------------------------------------------------------- -#Pauses the test and waits for you to press a confirmation button. Useful when -#you want to watch a test play out onscreen or inspect results. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Pauses the test and waits for you to press a confirmation button. Useful when +# you want to watch a test play out onscreen or inspect results. +# ------------------------------------------------------------------------------ func end_yielded_test(): _waiting = false -#------------------------------------------------------------------------------- -#Clears the text of the text box. This resets all counters. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Clears the text of the text box. This resets all counters. +# ------------------------------------------------------------------------------ func clear_text(): _ctrls.text_box.set_text("") _ctrls.text_box.clear_colors() update() -#------------------------------------------------------------------------------- -#Get the number of tests that were ran -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Get the number of tests that were ran +# ------------------------------------------------------------------------------ func get_test_count(): return _summary.tests -#------------------------------------------------------------------------------- -#Get the number of assertions that were made -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Get the number of assertions that were made +# ------------------------------------------------------------------------------ func get_assert_count(): return _summary.asserts -#------------------------------------------------------------------------------- -#Get the number of assertions that passed -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Get the number of assertions that passed +# ------------------------------------------------------------------------------ func get_pass_count(): return _summary.passed -#------------------------------------------------------------------------------- -#Get the number of assertions that failed -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Get the number of assertions that failed +# ------------------------------------------------------------------------------ func get_fail_count(): return _summary.failed -#------------------------------------------------------------------------------- -#Get the number of tests flagged as pending -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Get the number of tests flagged as pending +# ------------------------------------------------------------------------------ func get_pending_count(): return _summary.pending -#------------------------------------------------------------------------------- -#Set whether it should print to console or not. Default is yes. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Set whether it should print to console or not. Default is yes. +# ------------------------------------------------------------------------------ func set_should_print_to_console(should): _should_print_to_console = should -#------------------------------------------------------------------------------- -#Get whether it is printing to the console -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Get whether it is printing to the console +# ------------------------------------------------------------------------------ func get_should_print_to_console(): return _should_print_to_console -#------------------------------------------------------------------------------- -#Get the results of all tests ran as text. This string is the same as is -#displayed in the text box, and simlar to what is printed to the console. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Get the results of all tests ran as text. This string is the same as is +# displayed in the text box, and simlar to what is printed to the console. +# ------------------------------------------------------------------------------ func get_result_text(): return _log_text -#------------------------------------------------------------------------------- -#Set the log level. Use one of the various LOG_LEVEL_* constants. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Set the log level. Use one of the various LOG_LEVEL_* constants. +# ------------------------------------------------------------------------------ func set_log_level(level): _log_level = level _ctrls.log_level_slider.set_value(level) -#------------------------------------------------------------------------------- -#Get the current log level. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Get the current log level. +# ------------------------------------------------------------------------------ func get_log_level(): return _log_level -#------------------------------------------------------------------------------- -#DISABLED, does not work in 1.0 -#Call this method to make the test pause before teardown so that you can inspect -#anything that you have rendered to the screen. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Call this method to make the test pause before teardown so that you can inspect +# anything that you have rendered to the screen. +# ------------------------------------------------------------------------------ func pause_before_teardown(): _pause_before_teardown = true; -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # For batch processing purposes, you may want to ignore any calls to # pause_before_teardown that you forgot to remove. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func set_ignore_pause_before_teardown(should_ignore): _ignore_pause_before_teardown = should_ignore _ctrls.ignore_continue_checkbox.set_pressed(should_ignore) @@ -1428,25 +813,25 @@ func set_ignore_pause_before_teardown(should_ignore): func get_ignore_pause_before_teardown(): return _ignore_pause_before_teardown -#------------------------------------------------------------------------------- -#Set to true so that painting of the screen will occur between tests. Allows you -#to see the output as tests occur. Especially useful with long running tests that -#make it appear as though it has humg. +# ------------------------------------------------------------------------------ +# Set to true so that painting of the screen will occur between tests. Allows you +# to see the output as tests occur. Especially useful with long running tests that +# make it appear as though it has humg. # -#NOTE: not compatible with 1.0 so this is disabled by default. This will -#change in future releases. -#------------------------------------------------------------------------------- +# NOTE: not compatible with 1.0 so this is disabled by default. This will +# change in future releases. +# ------------------------------------------------------------------------------ func set_yield_between_tests(should): _yield_between.should = should func get_yield_between_tests(): return _yield_between.should -#------------------------------------------------------------------------------- -#Call _process or _fixed_process, if they exist, on obj and all it's children -#and their children and so and so forth. Delta will be passed through to all -#the _process or _fixed_process methods. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ +# Call _process or _fixed_process, if they exist, on obj and all it's children +# and their children and so and so forth. Delta will be passed through to all +# the _process or _fixed_process methods. +# ------------------------------------------------------------------------------ func simulate(obj, times, delta): for i in range(times): if(obj.has_method("_process")): @@ -1457,14 +842,14 @@ func simulate(obj, times, delta): for kid in obj.get_children(): simulate(kid, 1, delta) -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # Starts an internal timer with a timeout of the passed in time. A 'timeout' # signal will be sent when the timer ends. Returns itself so that it can be # used in a call to yield...cutting down on lines of code. # # Example, yield to the Gut object for 10 seconds: # yield(gut.set_yield_time(10), 'timeout') -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func set_yield_time(time, text=''): _yield_timer.set_wait_time(time) _yield_timer.start() @@ -1477,37 +862,37 @@ func set_yield_time(time, text=''): _set_yield_time_called = true return self -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # get the specific unit test that should be run -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func get_unit_test_name(): return _unit_test_name -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # set the specific unit test that should be run. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func set_unit_test_name(test_name): _unit_test_name = test_name -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # Creates an empty file at the specified path -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func file_touch(path): var f = File.new() f.open(path, f.WRITE) f.close() -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # deletes the file at the specified path -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func file_delete(path): var d = Directory.new() d.open(path.get_base_dir()) d.remove(path) -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # Checks to see if the passed in file has any data in it. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func is_file_empty(path): var f = File.new() f.open(path, f.READ) @@ -1515,18 +900,18 @@ func is_file_empty(path): f.close() return empty -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # deletes all files in a given directory -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func directory_delete_files(path): var d = Directory.new() d.open(path) - d.list_dir_begin() - #Traversing a directory is kinda odd. You have to start the process of listing - #the contents of a directory with list_dir_begin then use get_next until it - #returns an empty string. Then I guess you should end it. - var thing = d.get_next() + # Traversing a directory is kinda odd. You have to start the process of listing + # the contents of a directory with list_dir_begin then use get_next until it + # returns an empty string. Then I guess you should end it. + d.list_dir_begin() + var thing = d.get_next() # could be a dir or a file or something else maybe? var full_path = '' while(thing != ''): full_path = path + "/" + thing @@ -1536,6 +921,55 @@ func directory_delete_files(path): thing = d.get_next() d.list_dir_end() +# ------------------------------------------------------------------------------ +# Returns the instantiated script object that is currently being run. +# ------------------------------------------------------------------------------ +func get_current_script_object(): + var to_return = null + if(_test_script_objects.size() > 0): + to_return = _test_script_objects[-1] + return to_return + + +# ####################### +# Moved method warnings. +# ####################### +func moved_method(method_name): + p('[' + method_name + '] has been moved to the Test class. To fix, remove "gut." from in front of it.') + _test_script_objects[-1]._fail('Method has been moved.') + _summary.moved_methods += 1 +func assert_eq(got, expected, text=""): + moved_method('assert_eq') +func assert_ne(got, not_expected, text=""): + moved_method('assert_ne') +func assert_gt(got, expected, text=""): + moved_method('assert_gt') +func assert_lt(got, expected, text=""): + moved_method('assert_lt') +func assert_true(got, text=""): + moved_method('assert_true') +func assert_false(got, text=""): + moved_method('assert_false') +func assert_between(got, expect_low, expect_high, text=""): + moved_method('assert_between') +func assert_file_exists(file_path): + moved_method('assert_file_exists') +func assert_file_does_not_exist(file_path): + moved_method('assert_file_does_not_exist') +func assert_file_empty(file_path): + moved_method('assert_file_empty') +func assert_file_not_empty(file_path): + moved_method('assert_file_not_empty') +func assert_get_set_methods(obj, property, default, set_to): + moved_method('assert_get_set_methods') +func assert_has(obj, element, text=""): + moved_method('assert_has') +func assert_does_not_have(obj, element, text=""): + moved_method('assert_does_not_have') +func pending(text=""): + moved_method('pending') + + ################################################################################ # OneTest (INTERNAL USE ONLY) # Used to keep track of info about each test ran. diff --git a/addons/gut/gut_cmdln.gd b/addons/gut/gut_cmdln.gd index 2794c849..03630c61 100644 --- a/addons/gut/gut_cmdln.gd +++ b/addons/gut/gut_cmdln.gd @@ -5,7 +5,7 @@ #The MIT License (MIT) #===================== # -#Copyright (c) 2015 Tom "Butch" Wesley +#Copyright (c) 2017 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 @@ -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 4.0.0 +# Version 5.0.0 ################################################################################ extends SceneTree diff --git a/addons/gut/gut_gui.gd b/addons/gut/gut_gui.gd new file mode 100644 index 00000000..d6ec3703 --- /dev/null +++ b/addons/gut/gut_gui.gd @@ -0,0 +1,363 @@ +################################################################################ +#The MIT License (MIT) +#===================== +# +#Copyright (c) 2017 Tom "Butch" Wesley +# +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: +# +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. +# +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. +# +################################################################################ + +################################################################################ +# This class contains all the GUI creation code for Gut. It was split out and +# hopefully can be moved to a scene in the future. +################################################################################ +extends WindowDialog + +# various counters +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 + # update wasn't showing. Hack. + tally_passed = 0, + tally_failed = 0 +} + +var _is_running = false +var min_size = Vector2(650, 400) + +# This is the editor font. I imported using the font at +# https://github.com/godotengine/godot/tree/master/thirdparty/fonts +var _font = load('res://addons/gut/source_code_pro.fnt') + +#controls +var _ctrls = { + text_box = TextEdit.new(), + run_button = Button.new(), + copy_button = Button.new(), + clear_button = Button.new(), + continue_button = Button.new(), + log_level_slider = HSlider.new(), + scripts_drop_down = OptionButton.new(), + next_button = Button.new(), + previous_button = Button.new(), + stop_button = Button.new(), + script_progress = ProgressBar.new(), + test_progress = ProgressBar.new(), + runtime_label = Label.new(), + ignore_continue_checkbox = CheckBox.new(), + pass_count = Label.new(), + run_rest = Button.new() +} + +var _mouse_down = false +var _mouse_down_pos = null +var _mouse_in = false + +func _set_anchor_top_right(obj): + obj.set_anchor(MARGIN_RIGHT, ANCHOR_BEGIN) + obj.set_anchor(MARGIN_LEFT, ANCHOR_END) + obj.set_anchor(MARGIN_TOP, ANCHOR_BEGIN) + +func _set_anchor_bottom_right(obj): + obj.set_anchor(MARGIN_LEFT, ANCHOR_END) + obj.set_anchor(MARGIN_RIGHT, ANCHOR_END) + obj.set_anchor(MARGIN_TOP, ANCHOR_END) + obj.set_anchor(MARGIN_BOTTOM, ANCHOR_END) + +func _set_anchor_bottom_left(obj): + obj.set_anchor(MARGIN_LEFT, ANCHOR_BEGIN) + obj.set_anchor(MARGIN_TOP, ANCHOR_END) + obj.set_anchor(MARGIN_TOP, ANCHOR_END) + +#------------------------------------------------------------------------------- +#------------------------------------------------------------------------------- +func setup_controls(): + var button_size = Vector2(75, 35) + var button_spacing = Vector2(10, 0) + var pos = Vector2(0, 0) + + add_child(_ctrls.text_box) + _ctrls.text_box.set_size(Vector2(get_size().x - 4, 300)) + _ctrls.text_box.set_pos(Vector2(2, 0)) + _ctrls.text_box.set_readonly(true) + _ctrls.text_box.set_syntax_coloring(true) + _ctrls.text_box.set_anchor(MARGIN_LEFT, ANCHOR_BEGIN) + _ctrls.text_box.set_anchor(MARGIN_RIGHT, ANCHOR_END) + _ctrls.text_box.set_anchor(MARGIN_TOP, ANCHOR_BEGIN) + _ctrls.text_box.set_anchor(MARGIN_BOTTOM, ANCHOR_END) + _ctrls.text_box.add_font_override('font', _font) + + add_child(_ctrls.copy_button) + _ctrls.copy_button.set_text("Copy") + _ctrls.copy_button.set_size(button_size) + _ctrls.copy_button.set_pos(Vector2(get_size().x - 5 - button_size.x, _ctrls.text_box.get_size().y + 10)) + _set_anchor_bottom_right(_ctrls.copy_button) + + add_child(_ctrls.clear_button) + _ctrls.clear_button.set_text("Clear") + _ctrls.clear_button.set_size(button_size) + _ctrls.clear_button.set_pos(_ctrls.copy_button.get_pos() - Vector2(button_size.x, 0) - button_spacing) + _set_anchor_bottom_right(_ctrls.clear_button) + + add_child(_ctrls.pass_count) + _ctrls.pass_count.set_text('0 - 0') + _ctrls.pass_count.set_size(Vector2(100, 30)) + _ctrls.pass_count.set_pos(Vector2(550, 0)) + _ctrls.pass_count.set_align(HALIGN_RIGHT) + _set_anchor_top_right(_ctrls.pass_count) + + add_child(_ctrls.continue_button) + _ctrls.continue_button.set_text("Continue") + _ctrls.continue_button.set_size(Vector2(100, 25)) + _ctrls.continue_button.set_pos(Vector2(_ctrls.clear_button.get_pos().x, _ctrls.clear_button.get_pos().y + _ctrls.clear_button.get_size().y + 10)) + _ctrls.continue_button.set_disabled(true) + _set_anchor_bottom_right(_ctrls.continue_button) + + 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_pos(Vector2(_ctrls.continue_button.get_pos().x, _ctrls.continue_button.get_pos().y + _ctrls.continue_button.get_size().y - 5)) + _set_anchor_bottom_right(_ctrls.ignore_continue_checkbox) + + var log_label = Label.new() + add_child(log_label) + log_label.set_text("Log Level") + log_label.set_pos(Vector2(10, _ctrls.text_box.get_size().y + 1)) + _set_anchor_bottom_left(log_label) + + add_child(_ctrls.log_level_slider) + _ctrls.log_level_slider.set_size(Vector2(75, 30)) + _ctrls.log_level_slider.set_pos(Vector2(10, log_label.get_pos().y + 20)) + _ctrls.log_level_slider.set_min(0) + _ctrls.log_level_slider.set_max(2) + _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() + add_child(script_prog_label) + script_prog_label.set_pos(Vector2(100, log_label.get_pos().y)) + script_prog_label.set_text('Scripts:') + _set_anchor_bottom_left(script_prog_label) + + add_child(_ctrls.script_progress) + _ctrls.script_progress.set_size(Vector2(200, 10)) + _ctrls.script_progress.set_pos(script_prog_label.get_pos() + Vector2(70, 0)) + _ctrls.script_progress.set_min(0) + _ctrls.script_progress.set_max(1) + _ctrls.script_progress.set_unit_value(1) + _set_anchor_bottom_left(_ctrls.script_progress) + + var test_prog_label = Label.new() + add_child(test_prog_label) + test_prog_label.set_pos(Vector2(100, log_label.get_pos().y + 15)) + test_prog_label.set_text('Tests:') + _set_anchor_bottom_left(test_prog_label) + + add_child(_ctrls.test_progress) + _ctrls.test_progress.set_size(Vector2(200, 10)) + _ctrls.test_progress.set_pos(test_prog_label.get_pos() + Vector2(70, 0)) + _ctrls.test_progress.set_min(0) + _ctrls.test_progress.set_max(1) + _ctrls.test_progress.set_unit_value(1) + _set_anchor_bottom_left(_ctrls.test_progress) + + add_child(_ctrls.previous_button) + _ctrls.previous_button.set_size(Vector2(50, 25)) + pos = _ctrls.test_progress.get_pos() + Vector2(250, 25) + pos.x -= 300 + _ctrls.previous_button.set_pos(pos) + _ctrls.previous_button.set_text("<") + _set_anchor_bottom_left(_ctrls.previous_button) + + add_child(_ctrls.stop_button) + _ctrls.stop_button.set_size(Vector2(50, 25)) + pos.x += 60 + _ctrls.stop_button.set_pos(pos) + _ctrls.stop_button.set_text('stop') + _set_anchor_bottom_left(_ctrls.stop_button) + + add_child(_ctrls.run_rest) + _ctrls.run_rest.set_text('run') + _ctrls.run_rest.set_size(Vector2(50, 25)) + pos.x += 60 + _ctrls.run_rest.set_pos(pos) + _set_anchor_bottom_left(_ctrls.run_rest) + + add_child(_ctrls.next_button) + _ctrls.next_button.set_size(Vector2(50, 25)) + pos.x += 60 + _ctrls.next_button.set_pos(pos) + _ctrls.next_button.set_text(">") + _set_anchor_bottom_left(_ctrls.next_button) + + add_child(_ctrls.runtime_label) + _ctrls.runtime_label.set_text('0.0') + _ctrls.runtime_label.set_size(Vector2(50, 30)) + _ctrls.runtime_label.set_pos(Vector2(_ctrls.clear_button.get_pos().x - 90, _ctrls.next_button.get_pos().y)) + _set_anchor_bottom_right(_ctrls.runtime_label) + + # the drop down has to be one of the last added so that when then list of + # scripts is displayed, other controls do not get in the way of selecting + # an item in the list. + add_child(_ctrls.scripts_drop_down) + _ctrls.scripts_drop_down.set_size(Vector2(375, 25)) + _ctrls.scripts_drop_down.set_pos(Vector2(10, _ctrls.log_level_slider.get_pos().y + 50)) + _set_anchor_bottom_left(_ctrls.scripts_drop_down) + _ctrls.scripts_drop_down.set_clip_text(true) + + add_child(_ctrls.run_button) + _ctrls.run_button.set_text('<- run') + _ctrls.run_button.set_size(Vector2(50, 25)) + _ctrls.run_button.set_pos(_ctrls.scripts_drop_down.get_pos() + Vector2(_ctrls.scripts_drop_down.get_size().x + 5, 0)) + _set_anchor_bottom_left(_ctrls.run_button) + +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") + set_process(true) + set_pause_mode(PAUSE_MODE_PROCESS) + _update_controls() + +#------------------------------------------------------------------------------- +# Updates the display +#------------------------------------------------------------------------------- +func _update_controls(): + if(_is_running): + _ctrls.previous_button.set_disabled(true) + _ctrls.next_button.set_disabled(true) + _ctrls.pass_count.show() + else: + _ctrls.previous_button.set_disabled(_ctrls.scripts_drop_down.get_selected() == 0) + _ctrls.next_button.set_disabled(_ctrls.scripts_drop_down.get_selected() == _ctrls.scripts_drop_down.get_item_count() -1) + _ctrls.pass_count.hide() + + # disabled during run + _ctrls.run_button.set_disabled(_is_running) + _ctrls.run_rest.set_disabled(_is_running) + _ctrls.scripts_drop_down.set_disabled(_is_running) + + # enabled during run + _ctrls.stop_button.set_disabled(!_is_running) + _ctrls.pass_count.set_text(str( _summary.tally_passed, ' - ', _summary.tally_failed)) + + +#------------------------------------------------------------------------------- +#detect mouse movement +#------------------------------------------------------------------------------- +func _on_mouse_enter(): + _mouse_in = true + +#------------------------------------------------------------------------------- +#detect mouse movement +#------------------------------------------------------------------------------- +func _on_mouse_exit(): + _mouse_in = false + _mouse_down = false + + +#------------------------------------------------------------------------------- +#Send text box text to clipboard +#------------------------------------------------------------------------------- +func _copy_button_pressed(): + _ctrls.text_box.select_all() + _ctrls.text_box.copy() + + +#------------------------------------------------------------------------------- +#------------------------------------------------------------------------------- +func _init_run(): + _ctrls.text_box.clear_colors() + _ctrls.text_box.add_keyword_color("PASSED", Color(0, 1, 0)) + _ctrls.text_box.add_keyword_color("FAILED", Color(1, 0, 0)) + _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(_mouse_in): + #Check for mouse click inside the resize handle + if(event.type == InputEvent.MOUSE_BUTTON): + 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_pos().x - 10 and event.pos.y > get_size().y + get_pos().y - 10): + if event.pressed: + _mouse_down = true + _mouse_down_pos = event.pos + else: + _mouse_down = false + #Reszie + if(event.type == InputEvent.MOUSE_MOTION): + 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 + + if(new_size.x < min_size.x): + new_size.x = min_size.x + new_mouse_down_pos.x = _mouse_down_pos.x + + if(new_size.y < min_size.y): + new_size.y = min_size.y + new_mouse_down_pos.y = _mouse_down_pos.y + + _mouse_down_pos = new_mouse_down_pos + set_size(new_size) + +#------------------------------------------------------------------------------- +#Custom drawing to indicate results. +#------------------------------------------------------------------------------- +func _draw(): + #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 2b9479fc..5e8bc774 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="4.1.0" +version="5.0.0" script="gut_plugin.gd" diff --git a/addons/gut/signal_watcher.gd b/addons/gut/signal_watcher.gd index 92404c26..f6bf29ae 100644 --- a/addons/gut/signal_watcher.gd +++ b/addons/gut/signal_watcher.gd @@ -1,3 +1,28 @@ +################################################################################ +#The MIT License (MIT) +#===================== +# +#Copyright (c) 2017 Tom "Butch" Wesley +# +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: +# +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. +# +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. +# +################################################################################ # Some arbitrary string that should never show up by accident. If it does, then # shame on you. @@ -106,7 +131,7 @@ func get_signal_parameters(object, signal_name, index=-1): func is_watching_object(object): return _watched_signals.has(object) - + func is_watching(object, signal_name): return _watched_signals.has(object) and _watched_signals[object].has(signal_name) diff --git a/addons/gut/source_code_pro.fnt b/addons/gut/source_code_pro.fnt new file mode 100644 index 00000000..3367650f Binary files /dev/null and b/addons/gut/source_code_pro.fnt differ diff --git a/addons/gut/test.gd b/addons/gut/test.gd index 9cb8839e..ad67c6cd 100644 --- a/addons/gut/test.gd +++ b/addons/gut/test.gd @@ -5,7 +5,7 @@ #The MIT License (MIT) #===================== # -#Copyright (c) 2015 Tom "Butch" Wesley +#Copyright (c) 2017 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 @@ -28,129 +28,472 @@ ################################################################################ # View readme for usage details. # -# Version 4.0.0 +# Version - see gut.gd ################################################################################ # Class that all test scripts must extend. # -# Once a class extends this class it sent (via the numerous script loading -# methods) to a Gut object to run the tests. +# This provides all the asserts and other testing features. Test scripts are +# run by the Gut class in gut.gd ################################################################################ - extends Node + # constant for signal when calling yeild_for const YIELD = 'timeout' -#Need a reference to the instance that is running the tests. This -#is set by the gut class when it runs the tests. This gets you -#access to the asserts in the tests you write. + +# Need a reference to the instance that is running the tests. This +# is set by the gut class when it runs the tests. This gets you +# access to the asserts in the tests you write. var gut = null +var passed = false +var failed = false +var _disable_strict_datatype_checks = false +var _fail_pass_text = [] + +# Hash containing all the built in types in Godot. This provides an English +# name for the types that corosponds with the type constants defined in the +# engine. This is used for priting out messages when comparing types fails. +var types = {} + +func _init_types_dictionary(): + types[0] = 'TYPE_NIL' + types[1] = 'Bool' + types[2] = 'Int' + types[3] = 'Float/Real' + types[4] = 'String' + types[5] = 'Vector2' + types[6] = 'Rect2' + types[7] = 'Vector3' + types[8] = 'Matrix32' + types[9] = 'Plane' + types[10] = 'QUAT' + types[11] = 'AABB' + types[12] = 'Matrix3' + types[13] = 'Transform' + types[14] = 'Color' + types[15] = 'Image' + types[16] = 'Node Path' + types[17] = 'RID' + types[18] = 'Object' + types[19] = 'TYPE_INPUT_EVENT' + types[20] = 'Dictionary' + types[21] = 'Array' + types[22] = 'TYPE_RAW_ARRAY' + types[23] = 'TYPE_INT_ARRAY' + types[24] = 'TYPE_REAL_ARRAY' + types[25] = 'TYPE_STRING_ARRAY' + types[26] = 'TYPE_VECTOR2_ARRAY' + types[27] = 'TYPE_VECTOR3_ARRAY' + types[28] = 'TYPE_COLOR_ARRAY' + types[29] = 'TYPE_MAX' + +# Summary counts for the test. +var _summary = { + asserts = 0, + passed = 0, + failed = 0, + tests = 0, + pending = 0 +} +# This is used to watch signals so we can make assertions about them. +var _signal_watcher = load('res://addons/gut/signal_watcher.gd').new() + +func _init(): + _init_types_dictionary() # ####################### # Virtual Methods # ####################### -#Overridable method that runs before each test. +# Overridable method that runs before each test. func setup(): pass -#Overridable method that runs after each test +# Overridable method that runs after each test func teardown(): pass -#Overridable method that runs before any tests are run +# Overridable method that runs before any tests are run func prerun_setup(): pass -#Overridable method that runs after all tests are run +# Overridable method that runs after all tests are run func postrun_teardown(): pass -# ####################### -# Convenience Methods -# ####################### -# see gut method +# ------------------------------------------------------------------------------ +# Fail an assertion. Causes test and script to fail as well. +# ------------------------------------------------------------------------------ +func _fail(text): + _summary.asserts += 1 + _summary.failed += 1 + var msg = 'FAILED: ' + text + _fail_pass_text.append(msg) + if(gut): + gut.p(msg, gut.LOG_LEVEL_FAIL_ONLY) + gut._fail() + gut.end_yielded_test() + +# ------------------------------------------------------------------------------ +# Pass an assertion. +# ------------------------------------------------------------------------------ +func _pass(text): + _summary.asserts += 1 + _summary.passed += 1 + var msg = "PASSED: " + text + _fail_pass_text.append(msg) + if(gut): + gut.p(msg, gut.LOG_LEVEL_ALL_ASSERTS) + gut._pass() + 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 + + if(!_disable_strict_datatype_checks): + var got_type = typeof(got) + var expect_type = typeof(expected) + if(got_type != expect_type and got != null and expected != null): + # If we have a mismatch between float and int (types 2 and 3) then + # print out a warning but do not fail. + if([2, 3].has(got_type) and [2, 3].has(expect_type)): + if(gut): + gut.p(str('Warn: Float/Int comparison. Got ', types[got_type], ' but expected ', types[expect_type]), 1) + else: + _fail('Cannot compare ' + types[got_type] + '[' + str(got) + '] to ' + types[expect_type] + '[' + str(expected) + ']. ' + text) + passed = false + + return passed + +# ------------------------------------------------------------------------------ +# Asserts that the expected value equals the value got. +# ------------------------------------------------------------------------------ func assert_eq(got, expected, text=""): - gut.assert_eq(got, expected, text) + var disp = "[" + str(got) + "] expected to equal [" + str(expected) + "]: " + text + if(_do_datatypes_match__fail_if_not(got, expected, text)): + if(expected != got): + _fail(disp) + else: + _pass(disp) -# see gut method +# ------------------------------------------------------------------------------ +# Asserts that the value got does not equal the "not expected" value. +# ------------------------------------------------------------------------------ func assert_ne(got, not_expected, text=""): - gut.assert_ne(got, not_expected, text) - -# see gut method + var disp = "[" + str(got) + "] expected to be anything except [" + str(not_expected) + "]: " + text + if(_do_datatypes_match__fail_if_not(got, not_expected, text)): + if(got == not_expected): + _fail(disp) + else: + _pass(disp) +# ------------------------------------------------------------------------------ +# Asserts got is greater than expected +# ------------------------------------------------------------------------------ func assert_gt(got, expected, text=""): - gut.assert_gt(got, expected, text) + var disp = "[" + str(got) + "] expected to be > than [" + str(expected) + "]: " + text + if(_do_datatypes_match__fail_if_not(got, expected, text)): + if(got > expected): + _pass(disp) + else: + _fail(disp) -# see gut method +# ------------------------------------------------------------------------------ +# Asserts got is less than expected +# ------------------------------------------------------------------------------ func assert_lt(got, expected, text=""): - gut.assert_lt(got, expected, text) + var disp = "[" + str(got) + "] expected to be < than [" + str(expected) + "]: " + text + if(_do_datatypes_match__fail_if_not(got, expected, text)): + if(got < expected): + _pass(disp) + else: + _fail(disp) -# see gut method +# ------------------------------------------------------------------------------ +# asserts that got is true +# ------------------------------------------------------------------------------ func assert_true(got, text=""): - gut.assert_true(got, text) + if(!got): + _fail(text) + else: + _pass(text) -# see gut method +# ------------------------------------------------------------------------------ +# Asserts that got is false +# ------------------------------------------------------------------------------ func assert_false(got, text=""): - gut.assert_false(got, text) + if(got): + _fail(text) + else: + _pass(text) -# see gut method +# ------------------------------------------------------------------------------ +# Asserts value is between (inclusive) the two expected values. +# ------------------------------------------------------------------------------ func assert_between(got, expect_low, expect_high, text=""): - gut.assert_between(got, expect_low, expect_high, text) + var disp = "[" + str(got) + "] expected to be between [" + str(expect_low) + "] and [" + str(expect_high) + "]: " + text + + if(_do_datatypes_match__fail_if_not(got, expect_low, text) and _do_datatypes_match__fail_if_not(got, expect_high, text)): + if(expect_low > expect_high): + disp = "INVALID range. [" + str(expect_low) + "] is not less than [" + str(expect_high) + "]" + _fail(disp) + else: + if(got < expect_low or got > expect_high): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Uses the 'has' method of the object passed in to determine if it contains +# the passed in element. +# ------------------------------------------------------------------------------ +func assert_has(obj, element, text=""): + var disp = str('Expected [', obj, '] to contain value: [', element, ']: ', text) + if(obj.has(element)): + _pass(disp) + else: + _fail(disp) -# see gut method +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +func assert_does_not_have(obj, element, text=""): + var disp = str('Expected [', obj, '] to NOT contain value: [', element, ']: ', text) + if(obj.has(element)): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Asserts that a file exists +# ------------------------------------------------------------------------------ func assert_file_exists(file_path): - gut.assert_file_exists(file_path) + var disp = 'expected [' + file_path + '] to exist.' + var f = File.new() + if(f.file_exists(file_path)): + _pass(disp) + else: + _fail(disp) -# see gut method +# ------------------------------------------------------------------------------ +# Asserts that a file should not exist +# ------------------------------------------------------------------------------ func assert_file_does_not_exist(file_path): - gut.assert_file_does_not_exist(file_path) + var disp = 'expected [' + file_path + '] to NOT exist' + var f = File.new() + if(!f.file_exists(file_path)): + _pass(disp) + else: + _fail(disp) -# see gut method +# ------------------------------------------------------------------------------ +# Asserts the specified file is empty +# ------------------------------------------------------------------------------ func assert_file_empty(file_path): - gut.assert_file_empty(file_path) + var disp = 'expected [' + file_path + '] to be empty' + var f = File.new() + if(f.file_exists(file_path) and gut.is_file_empty(file_path)): + _pass(disp) + else: + _fail(disp) -# see gut method +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ func assert_file_not_empty(file_path): - gut.assert_file_not_empty(file_path) + var disp = 'expected [' + file_path + '] to contain data' + if(!gut.is_file_empty(file_path)): + _pass(disp) + else: + _fail(disp) -# see gut method +# ------------------------------------------------------------------------------ +# Verifies the object has get and set methods for the property passed in. The +# property isn't tied to anything, just a name to be appended to the end of +# get_ and set_. Asserts the get_ and set_ methods exist, if not, it stops there. +# If they exist then it asserts get_ returns the expected default then calls +# set_ and asserts get_ has the value it was set to. +# ------------------------------------------------------------------------------ func assert_get_set_methods(obj, property, default, set_to): - gut.assert_get_set_methods(obj, property, default, set_to) + var fail_count = _summary.failed + var get = 'get_' + property + var set = 'set_' + property + assert_true(obj.has_method(get), 'Should have get method: ' + get) + assert_true(obj.has_method(set), 'Should have set method: ' + set) + if(_summary.failed > fail_count): + return + assert_eq(obj.call(get), default, 'It should have the expected default value.') + obj.call(set, set_to) + assert_eq(obj.call(get), set_to, 'The set value should have been returned.') -func assert_has(obj, element, text=""): - gut.assert_has(obj, element, text) -func assert_does_not_have(obj, element, text=""): - gut.assert_does_not_have(obj, element, text) +# ------------------------------------------------------------------------------ +# Signal assertion helper. Do not call directly, use _can_make_signal_assertions +# ------------------------------------------------------------------------------ +func _fail_if_does_not_have_signal(object, signal_name): + var did_fail = false + if(!object.has_user_signal(signal_name)): + _fail(str('Object ', object, ' does not have the signal [', signal_name, ']')) + did_fail = true + return did_fail +# ------------------------------------------------------------------------------ +# Signal assertion helper. Do not call directly, use _can_make_signal_assertions +# ------------------------------------------------------------------------------ +func _fail_if_not_watching(object): + var did_fail = false + if(!_signal_watcher.is_watching_object(object)): + _fail(str('Cannot make signal assertions because the object ', object, \ + ' is not being watched. Call watch_signals(some_object) to be able to make assertions about signals.')) + did_fail = true + return did_fail + +# ------------------------------------------------------------------------------ +# Signal assertion helper. +# +# Verifies that the object and signal are valid for making signal assertions. +# This will fail with specific messages that indicate why they are not valid. +# This returns true/false to indicate if the object and signal are valid. +# ------------------------------------------------------------------------------ +func _can_make_signal_assertions(object, signal_name): + return !(_fail_if_not_watching(object) or _fail_if_does_not_have_signal(object, signal_name)) +# ------------------------------------------------------------------------------ +# Watch the signals for an object. This must be called before you can make +# any assertions about the signals themselves. +# ------------------------------------------------------------------------------ func watch_signals(object): - gut.watch_signals(object) + _signal_watcher.watch_signals(object) +# ------------------------------------------------------------------------------ +# Asserts that a signal has been emitted at least once. +# +# This will fail with specific messages if the object is not being watched or +# the object does not have the specified signal +# ------------------------------------------------------------------------------ func assert_signal_emitted(object, signal_name, text=""): - gut.assert_signal_emitted(object, signal_name, text) - -func assert_signal_emitted_with_parameters(object, signal_name, parameters, index=-1): - gut.assert_signal_emitted_with_parameters(object, signal_name, parameters, index) + var disp = str('Expected object ', object, ' to emit signal [', signal_name, ']: ', text) + if(_can_make_signal_assertions(object, signal_name)): + if(_signal_watcher.did_emit(object, signal_name)): + _pass(disp) + else: + _fail(disp) +# ------------------------------------------------------------------------------ +# Asserts that a signal has not been emitted. +# +# This will fail with specific messages if the object is not being watched or +# the object does not have the specified signal +# ------------------------------------------------------------------------------ func assert_signal_not_emitted(object, signal_name, text=""): - gut.assert_signal_not_emitted(object, signal_name, text) + var disp = str('Expected object ', object, ' to NOT emit signal [', signal_name, ']: ', text) + if(_can_make_signal_assertions(object, signal_name)): + if(_signal_watcher.did_emit(object, signal_name)): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Asserts that a signal was fired with the specified parameters. The expected +# parameters should be passed in as an array. An optional index can be passed +# when a signal has fired more than once. The default is to retrieve the most +# recent emission of the signal. +# +# This will fail with specific messages if the object is not being watched or +# the object does not have the specified signal +# ------------------------------------------------------------------------------ +func assert_signal_emitted_with_parameters(object, signal_name, parameters, index=-1): + var disp = str('Expected object ', object, ' to emit signal [', signal_name, '] with parameters ', parameters, ', got ') + if(_can_make_signal_assertions(object, signal_name)): + if(_signal_watcher.did_emit(object, signal_name)): + var parms_got = _signal_watcher.get_signal_parameters(object, signal_name, index) + if(parameters == parms_got): + _pass(str(disp, parms_got)) + else: + _fail(str(disp, parms_got)) + else: + _fail(str('Object ', object, ' did not emit signal [', signal_name, ']')) +# ------------------------------------------------------------------------------ +# Assert that a signal has been emitted a specific number of times. +# +# This will fail with specific messages if the object is not being watched or +# the object does not have the specified signal +# ------------------------------------------------------------------------------ func assert_signal_emit_count(object, signal_name, times, text=""): - gut.assert_signal_emit_count(object, signal_name, times, text) + if(_can_make_signal_assertions(object, signal_name)): + var count = _signal_watcher.get_emit_count(object, signal_name) + var disp = str('Expected the signal [', signal_name, '] emit count of [', count, '] to equal [', times, ']: ', text) + if(count== times): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Assert that the passed in object has the specfied signal +# ------------------------------------------------------------------------------ func assert_has_signal(object, signal_name, text=""): - gut.assert_has_signal(object, signal_name, text) + var disp = str('Expected object ', object, ' to have signal [', signal_name, ']: ', text) + if(object.has_user_signal(signal_name)): + _pass(disp) + else: + _fail(disp) -func get_signal_parameters(object, signal_name, index=-1): - return gut.get_signal_parameters(object, signal_name, index) -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ # Returns the number of times a signal was emitted. -1 returned if the object # is not being watched. -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------ func get_signal_emit_count(object, signal_name): - return gut.get_signal_emit_count(object, signal_name) + return _signal_watcher.get_emit_count(object, signal_name) + +# ------------------------------------------------------------------------------ +# Get the parmaters of a fired signal. If the signal was not fired null is +# returned. You can specify an optional index (use get_signal_emit_count to +# determine the number of times it was emitted). The default index is the +# latest time the signal was fired (size() -1 insetead of 0). The parameters +# returned are in an array. +# ------------------------------------------------------------------------------ +func get_signal_parameters(object, signal_name, index=-1): + return _signal_watcher.get_signal_parameters(object, signal_name, index) -# see gut method +# ------------------------------------------------------------------------------ +# Assert that object is an instance of a_class +# ------------------------------------------------------------------------------ +func assert_extends(object, a_class, text=''): + var disp = str('Expected [', object, '] to be type of [', a_class, ']: ', text) + var NATIVE_CLASS = 'GDNativeClass' + var GDSCRIPT_CLASS = 'GDScript' + var bad_param_2 = 'Parameter 2 must be a Class (like Node2D or Label). You passed ' + + if(typeof(object) != 18): + _fail(str('Parameter 1 must be an instance of an object. You passed: ', types[typeof(object)])) + elif(typeof(a_class) != 18): + _fail(str(bad_param_2, types[typeof(a_class)])) + else: + disp = str('Expected [', object.get_type(), '] to extend [', a_class.get_type(), ']: ', text) + if(a_class.get_type() != NATIVE_CLASS and a_class.get_type() != GDSCRIPT_CLASS): + _fail(str(bad_param_2, types[typeof(a_class)])) + else: + if(object extends a_class): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Mark the current test as pending. +# ------------------------------------------------------------------------------ func pending(text=""): - gut.pending(text) + _summary.pending += 1 + if(gut): + if(text == ""): + gut.p("Pending") + else: + gut.p("Pending: " + text) + gut.end_yielded_test() + +# ------------------------------------------------------------------------------ +# Returns the number of times a signal was emitted. -1 returned if the object +# is not being watched. +# ------------------------------------------------------------------------------ # I think this reads better than set_yield_time, but don't want to break anything func yield_for(time, msg=''): @@ -158,3 +501,38 @@ func yield_for(time, msg=''): func end_test(): gut.end_yielded_test() + +func get_summary(): + return _summary + +func get_fail_count(): + return _summary.failed + +func get_pass_count(): + return _summary.passed + +func get_pending_count(): + return _summary.pending + +func get_assert_count(): + return _summary.asserts + +func clear_signal_watcher(): + _signal_watcher.clear() + +# ------------------------------------------------------------------------------ +# Convert the _summary dictionary into text +# ------------------------------------------------------------------------------ +func get_summary_text(): + var to_return = get_script().get_path() + "\n" + to_return += str(' ', _summary.passed, ' of ', _summary.asserts, ' passed.') + if(_summary.pending > 0): + 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