summaryrefslogtreecommitdiff
path: root/addons/gut/gut_cmdln.gd
diff options
context:
space:
mode:
Diffstat (limited to 'addons/gut/gut_cmdln.gd')
-rw-r--r--addons/gut/gut_cmdln.gd308
1 files changed, 308 insertions, 0 deletions
diff --git a/addons/gut/gut_cmdln.gd b/addons/gut/gut_cmdln.gd
new file mode 100644
index 0000000..e1a0c0a
--- /dev/null
+++ b/addons/gut/gut_cmdln.gd
@@ -0,0 +1,308 @@
+# ##############################################################################
+#(G)odot (U)nit (T)est class
+#
+# ##############################################################################
+# The MIT License (MIT)
+# =====================
+#
+# Copyright (c) 2020 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.
+#
+# ##############################################################################
+# Description
+# -----------
+# Command line interface for the GUT unit testing tool. Allows you to run tests
+# from the command line instead of running a scene. Place this script along with
+# gut.gd into your scripts directory at the root of your project. Once there you
+# can run this script (from the root of your project) using the following command:
+# godot -s -d test/gut/gut_cmdln.gd
+#
+# 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.
+# ##############################################################################
+extends SceneTree
+
+var Optparse = load('res://addons/gut/optparse.gd')
+var Gut = load('res://addons/gut/gut.gd')
+var GutRunner = load('res://addons/gut/gui/GutRunner.tscn')
+
+# ------------------------------------------------------------------------------
+# Helper class to resolve the various different places where an option can
+# be set. Using the get_value method will enforce the order of precedence of:
+# 1. command line value
+# 2. config file value
+# 3. default value
+#
+# The idea is that you set the base_opts. That will get you a copies of the
+# hash with null values for the other types of values. Lower precedented hashes
+# will punch through null values of higher precedented hashes.
+# ------------------------------------------------------------------------------
+class OptionResolver:
+ var base_opts = null
+ var cmd_opts = null
+ var config_opts = null
+
+
+ func get_value(key):
+ return _nvl(cmd_opts[key], _nvl(config_opts[key], base_opts[key]))
+
+ func set_base_opts(opts):
+ base_opts = opts
+ cmd_opts = _null_copy(opts)
+ config_opts = _null_copy(opts)
+
+ # creates a copy of a hash with all values null.
+ func _null_copy(h):
+ var new_hash = {}
+ for key in h:
+ new_hash[key] = null
+ return new_hash
+
+ func _nvl(a, b):
+ if(a == null):
+ return b
+ else:
+ return a
+ func _string_it(h):
+ var to_return = ''
+ for key in h:
+ to_return += str('(',key, ':', _nvl(h[key], 'NULL'), ')')
+ return to_return
+
+ func to_s():
+ return str("base:\n", _string_it(base_opts), "\n", \
+ "config:\n", _string_it(config_opts), "\n", \
+ "cmd:\n", _string_it(cmd_opts), "\n", \
+ "resolved:\n", _string_it(get_resolved_values()))
+
+ func get_resolved_values():
+ var to_return = {}
+ for key in base_opts:
+ to_return[key] = get_value(key)
+ return to_return
+
+ func to_s_verbose():
+ var to_return = ''
+ var resolved = get_resolved_values()
+ for key in base_opts:
+ to_return += str(key, "\n")
+ to_return += str(' default: ', _nvl(base_opts[key], 'NULL'), "\n")
+ to_return += str(' config: ', _nvl(config_opts[key], ' --'), "\n")
+ to_return += str(' cmd: ', _nvl(cmd_opts[key], ' --'), "\n")
+ to_return += str(' final: ', _nvl(resolved[key], 'NULL'), "\n")
+
+ return to_return
+
+# ------------------------------------------------------------------------------
+# Here starts the actual script that uses the Options class to kick off Gut
+# and run your tests.
+# ------------------------------------------------------------------------------
+var _utils = load('res://addons/gut/utils.gd').get_instance()
+var _gut_config = load('res://addons/gut/gut_config.gd').new()
+# instance of gut
+var _tester = null
+# array of command line options specified
+var _final_opts = []
+
+
+func setup_options(options, font_names):
+ var opts = Optparse.new()
+ opts.set_banner(('This is the command line interface for the unit testing tool Gut. With this ' +
+ 'interface you can run one or more test scripts from the command line. In order ' +
+ 'for the Gut options to not clash with any other godot options, each option starts ' +
+ 'with a "g". Also, any option that requires a value will take the form of ' +
+ '"-g<name>=<value>". There cannot be any spaces between the option, the "=", or ' +
+ 'inside a specified value or godot will think you are trying to run a scene.'))
+ opts.add('-gtest', [], 'Comma delimited list of full paths to test scripts to run.')
+ opts.add('-gdir', options.dirs, 'Comma delimited list of directories to add tests from.')
+ opts.add('-gprefix', options.prefix, 'Prefix used to find tests when specifying -gdir. Default "[default]".')
+ opts.add('-gsuffix', options.suffix, 'Suffix used to find tests when specifying -gdir. Default "[default]".')
+ opts.add('-ghide_orphans', false, 'Display orphan counts for tests and scripts. Default "[default]".')
+ opts.add('-gmaximize', false, 'Maximizes test runner window to fit the viewport.')
+ opts.add('-gcompact_mode', false, 'The runner will be in compact mode. This overrides -gmaximize.')
+ opts.add('-gexit', false, 'Exit after running tests. If not specified you have to manually close the window.')
+ opts.add('-gexit_on_success', false, 'Only exit if all tests pass.')
+ opts.add('-glog', options.log_level, 'Log level. Default [default]')
+ opts.add('-gignore_pause', false, 'Ignores any calls to gut.pause_before_teardown.')
+ opts.add('-gselect', '', ('Select a script to run initially. The first script that ' +
+ 'was loaded using -gtest or -gdir that contains the specified ' +
+ 'string will be executed. You may run others by interacting ' +
+ 'with the GUI.'))
+ opts.add('-gunit_test_name', '', ('Name of a test to run. Any test that contains the specified ' +
+ 'text will be run, all others will be skipped.'))
+ opts.add('-gh', false, 'Print this help, then quit')
+ opts.add('-gconfig', 'res://.gutconfig.json', 'A config file that contains configuration information. Default is res://.gutconfig.json')
+ opts.add('-ginner_class', '', 'Only run inner classes that contain this string')
+ opts.add('-gopacity', options.opacity, 'Set opacity of test runner window. Use range 0 - 100. 0 = transparent, 100 = opaque.')
+ opts.add('-gpo', false, 'Print option values from all sources and the value used, then quit.')
+ opts.add('-ginclude_subdirs', false, 'Include subdirectories of -gdir.')
+ opts.add('-gdouble_strategy', 'partial', 'Default strategy to use when doubling. Valid values are [partial, full]. Default "[default]"')
+ opts.add('-gdisable_colors', false, 'Disable command line colors.')
+ opts.add('-gpre_run_script', '', 'pre-run hook script path')
+ opts.add('-gpost_run_script', '', 'post-run hook script path')
+ opts.add('-gprint_gutconfig_sample', false, 'Print out json that can be used to make a gutconfig file then quit.')
+
+ opts.add('-gfont_name', options.font_name, str('Valid values are: ', font_names, '. Default "[default]"'))
+ opts.add('-gfont_size', options.font_size, 'Font size, default "[default]"')
+ opts.add('-gbackground_color', options.background_color, 'Background color as an html color, default "[default]"')
+ opts.add('-gfont_color',options.font_color, 'Font color as an html color, default "[default]"')
+
+ opts.add('-gjunit_xml_file', options.junit_xml_file, 'Export results of run to this file in the Junit XML format.')
+ opts.add('-gjunit_xml_timestamp', options.junit_xml_timestamp, 'Include a timestamp in the -gjunit_xml_file, default [default]')
+ return opts
+
+
+# Parses options, applying them to the _tester or setting values
+# in the options struct.
+func extract_command_line_options(from, to):
+ to.config_file = from.get_value('-gconfig')
+ to.dirs = from.get_value('-gdir')
+ to.disable_colors = from.get_value('-gdisable_colors')
+ to.double_strategy = from.get_value('-gdouble_strategy')
+ to.ignore_pause = from.get_value('-gignore_pause')
+ to.include_subdirs = from.get_value('-ginclude_subdirs')
+ to.inner_class = from.get_value('-ginner_class')
+ to.log_level = from.get_value('-glog')
+ to.opacity = from.get_value('-gopacity')
+ to.post_run_script = from.get_value('-gpost_run_script')
+ to.pre_run_script = from.get_value('-gpre_run_script')
+ to.prefix = from.get_value('-gprefix')
+ to.selected = from.get_value('-gselect')
+ to.should_exit = from.get_value('-gexit')
+ to.should_exit_on_success = from.get_value('-gexit_on_success')
+ to.should_maximize = from.get_value('-gmaximize')
+ to.compact_mode = from.get_value('-gcompact_mode')
+ to.hide_orphans = from.get_value('-ghide_orphans')
+ to.suffix = from.get_value('-gsuffix')
+ to.tests = from.get_value('-gtest')
+ to.unit_test_name = from.get_value('-gunit_test_name')
+
+ to.font_size = from.get_value('-gfont_size')
+ to.font_name = from.get_value('-gfont_name')
+ to.background_color = from.get_value('-gbackground_color')
+ to.font_color = from.get_value('-gfont_color')
+
+ to.junit_xml_file = from.get_value('-gjunit_xml_file')
+ to.junit_xml_timestamp = from.get_value('-gjunit_xml_timestamp')
+
+
+
+func _print_gutconfigs(values):
+ var header = """Here is a sample of a full .gutconfig.json file.
+You do not need to specify all values in your own file. The values supplied in
+this sample are what would be used if you ran gut w/o the -gprint_gutconfig_sample
+option (option priority: command-line, .gutconfig, default)."""
+ print("\n", header.replace("\n", ' '), "\n\n")
+ var resolved = values
+
+ # remove some options that don't make sense to be in config
+ resolved.erase("config_file")
+ resolved.erase("show_help")
+
+ print("Here's a config with all the properties set based off of your current command and config.")
+ print(JSON.print(resolved, ' '))
+
+ for key in resolved:
+ resolved[key] = null
+
+ print("\n\nAnd here's an empty config for you fill in what you want.")
+ print(JSON.print(resolved, ' '))
+
+
+# parse options and run Gut
+func _run_gut():
+ var opt_resolver = OptionResolver.new()
+ opt_resolver.set_base_opts(_gut_config.default_options)
+
+ print("\n\n", ' --- Gut ---')
+ var o = setup_options(_gut_config.default_options, _gut_config.valid_fonts)
+
+ var all_options_valid = o.parse()
+ extract_command_line_options(o, opt_resolver.cmd_opts)
+
+ var load_result = _gut_config.load_options_no_defaults(
+ opt_resolver.get_value('config_file'))
+
+ # SHORTCIRCUIT
+ if(!all_options_valid or load_result == -1):
+ quit(1)
+ else:
+ opt_resolver.config_opts = _gut_config.options
+
+ if(o.get_value('-gh')):
+ print(_utils.get_version_text())
+ o.print_help()
+ quit()
+ elif(o.get_value('-gpo')):
+ print('All command line options and where they are specified. ' +
+ 'The "final" value shows which value will actually be used ' +
+ 'based on order of precedence (default < .gutconfig < cmd line).' + "\n")
+ print(opt_resolver.to_s_verbose())
+ quit()
+ elif(o.get_value('-gprint_gutconfig_sample')):
+ _print_gutconfigs(opt_resolver.get_resolved_values())
+ quit()
+ else:
+ _final_opts = opt_resolver.get_resolved_values();
+ _gut_config.options = _final_opts
+
+ var runner = GutRunner.instance()
+ runner.set_cmdln_mode(true)
+ runner.set_gut_config(_gut_config)
+
+ _tester = runner.get_gut()
+ _tester.connect('tests_finished', self, '_on_tests_finished',
+ [_final_opts.should_exit, _final_opts.should_exit_on_success])
+
+ get_root().add_child(runner)
+ runner.run_tests()
+
+
+# exit if option is set.
+func _on_tests_finished(should_exit, should_exit_on_success):
+ if(_final_opts.dirs.size() == 0):
+ if(_tester.get_summary().get_totals().scripts == 0):
+ var lgr = _tester.get_logger()
+ lgr.error('No directories configured. Add directories with options or a .gutconfig.json file. Use the -gh option for more information.')
+
+ if(_tester.get_fail_count()):
+ OS.exit_code = 1
+
+ # Overwrite the exit code with the post_script
+ var post_inst = _tester.get_post_run_script_instance()
+ if(post_inst != null and post_inst.get_exit_code() != null):
+ OS.exit_code = post_inst.get_exit_code()
+
+ if(should_exit or (should_exit_on_success and _tester.get_fail_count() == 0)):
+ quit()
+ else:
+ print("Tests finished, exit manually")
+
+# ------------------------------------------------------------------------------
+# MAIN
+# ------------------------------------------------------------------------------
+func _init():
+ if(!_utils.is_version_ok()):
+ print("\n\n", _utils.get_version_text())
+ push_error(_utils.get_bad_version_text())
+ OS.exit_code = 1
+ quit()
+ else:
+ _run_gut()