Adding log.gd
This commit is contained in:
parent
eb32d6614e
commit
4522259397
547 changed files with 46844 additions and 0 deletions
110
addons/gdUnit4/bin/GdUnitBuildTool.gd
Normal file
110
addons/gdUnit4/bin/GdUnitBuildTool.gd
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env -S godot -s
|
||||
extends SceneTree
|
||||
|
||||
enum {
|
||||
INIT,
|
||||
PROCESSING,
|
||||
EXIT
|
||||
}
|
||||
|
||||
const RETURN_SUCCESS = 0
|
||||
const RETURN_ERROR = 100
|
||||
const RETURN_WARNING = 101
|
||||
|
||||
var _console := CmdConsole.new()
|
||||
var _cmd_options: = CmdOptions.new([
|
||||
CmdOption.new(
|
||||
"-scp, --src_class_path",
|
||||
"-scp <source_path>",
|
||||
"The full class path of the source file.",
|
||||
TYPE_STRING
|
||||
),
|
||||
CmdOption.new(
|
||||
"-scl, --src_class_line",
|
||||
"-scl <line_number>",
|
||||
"The selected line number to generate test case.",
|
||||
TYPE_INT
|
||||
)
|
||||
])
|
||||
|
||||
var _status := INIT
|
||||
var _source_file :String = ""
|
||||
var _source_line :int = -1
|
||||
|
||||
|
||||
func _init():
|
||||
var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitBuildTool.gd")
|
||||
var result := cmd_parser.parse(OS.get_cmdline_args())
|
||||
if result.is_error():
|
||||
show_options()
|
||||
exit(RETURN_ERROR, result.error_message());
|
||||
return
|
||||
var cmd_options = result.value()
|
||||
for cmd in cmd_options:
|
||||
if cmd.name() == '-scp':
|
||||
_source_file = cmd.arguments()[0]
|
||||
_source_file = ProjectSettings.localize_path(ProjectSettings.localize_path(_source_file))
|
||||
if cmd.name() == '-scl':
|
||||
_source_line = int(cmd.arguments()[0])
|
||||
# verify required arguments
|
||||
if _source_file == "":
|
||||
exit(RETURN_ERROR, "missing required argument -scp <source>")
|
||||
return
|
||||
if _source_line == -1:
|
||||
exit(RETURN_ERROR, "missing required argument -scl <number>")
|
||||
return
|
||||
_status = PROCESSING
|
||||
|
||||
|
||||
func _idle(_delta):
|
||||
if _status == PROCESSING:
|
||||
var script := ResourceLoader.load(_source_file) as Script
|
||||
if script == null:
|
||||
exit(RETURN_ERROR, "Can't load source file %s!" % _source_file)
|
||||
var result := GdUnitTestSuiteBuilder.create(script, _source_line)
|
||||
if result.is_error():
|
||||
print_json_error(result.error_message())
|
||||
exit(RETURN_ERROR, result.error_message())
|
||||
return
|
||||
_console.prints_color("Added testcase: %s" % result.value(), Color.CORNFLOWER_BLUE)
|
||||
print_json_result(result.value())
|
||||
exit(RETURN_SUCCESS)
|
||||
|
||||
|
||||
func exit(code :int, message :String = "") -> void:
|
||||
_status = EXIT
|
||||
if code == RETURN_ERROR:
|
||||
if not message.is_empty():
|
||||
_console.prints_error(message)
|
||||
_console.prints_error("Abnormal exit with %d" % code)
|
||||
else:
|
||||
_console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON)
|
||||
quit(code)
|
||||
|
||||
|
||||
func print_json_result(result :Dictionary) -> void:
|
||||
# convert back to system path
|
||||
var path = ProjectSettings.globalize_path(result["path"]);
|
||||
var json = 'JSON_RESULT:{"TestCases" : [{"line":%d, "path": "%s"}]}' % [result["line"], path]
|
||||
prints(json)
|
||||
|
||||
|
||||
func print_json_error(error :String) -> void:
|
||||
prints('JSON_RESULT:{"Error" : "%s"}' % error)
|
||||
|
||||
|
||||
func show_options() -> void:
|
||||
_console.prints_color(" Usage:", Color.DARK_SALMON)
|
||||
_console.prints_color(" build -scp <source_path> -scl <line_number>", Color.DARK_SALMON)
|
||||
_console.prints_color("-- Options ---------------------------------------------------------------------------------------",
|
||||
Color.DARK_SALMON).new_line()
|
||||
for option in _cmd_options.default_options():
|
||||
descripe_option(option)
|
||||
|
||||
|
||||
func descripe_option(cmd_option :CmdOption) -> void:
|
||||
_console.print_color(" %-40s" % str(cmd_option.commands()), Color.CORNFLOWER_BLUE)
|
||||
_console.prints_color(cmd_option.description(), Color.LIGHT_GREEN)
|
||||
if not cmd_option.help().is_empty():
|
||||
_console.prints_color("%-4s %s" % ["", cmd_option.help()], Color.DARK_TURQUOISE)
|
||||
_console.new_line()
|
||||
600
addons/gdUnit4/bin/GdUnitCmdTool.gd
Normal file
600
addons/gdUnit4/bin/GdUnitCmdTool.gd
Normal file
|
|
@ -0,0 +1,600 @@
|
|||
#!/usr/bin/env -S godot -s
|
||||
extends SceneTree
|
||||
|
||||
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
|
||||
|
||||
#warning-ignore-all:return_value_discarded
|
||||
class CLIRunner:
|
||||
extends Node
|
||||
|
||||
enum {
|
||||
READY,
|
||||
INIT,
|
||||
RUN,
|
||||
STOP,
|
||||
EXIT
|
||||
}
|
||||
|
||||
const DEFAULT_REPORT_COUNT = 20
|
||||
const RETURN_SUCCESS = 0
|
||||
const RETURN_ERROR = 100
|
||||
const RETURN_ERROR_HEADLESS_NOT_SUPPORTED = 103
|
||||
const RETURN_ERROR_GODOT_VERSION_NOT_SUPPORTED = 104
|
||||
const RETURN_WARNING = 101
|
||||
|
||||
var _state = READY
|
||||
var _test_suites_to_process: Array
|
||||
var _executor
|
||||
var _cs_executor
|
||||
var _report: GdUnitHtmlReport
|
||||
var _report_dir: String
|
||||
var _report_max: int = DEFAULT_REPORT_COUNT
|
||||
var _headless_mode_ignore := false
|
||||
var _runner_config := GdUnitRunnerConfig.new()
|
||||
var _console := CmdConsole.new()
|
||||
var _cmd_options := CmdOptions.new([
|
||||
CmdOption.new(
|
||||
"-a, --add",
|
||||
"-a <directory|path of testsuite>",
|
||||
"Adds the given test suite or directory to the execution pipeline.",
|
||||
TYPE_STRING
|
||||
),
|
||||
CmdOption.new(
|
||||
"-i, --ignore",
|
||||
"-i <testsuite_name|testsuite_name:test-name>",
|
||||
"Adds the given test suite or test case to the ignore list.",
|
||||
TYPE_STRING
|
||||
),
|
||||
CmdOption.new(
|
||||
"-c, --continue",
|
||||
"",
|
||||
"""By default GdUnit will abort checked first test failure to be fail fast,
|
||||
instead of stop after first failure you can use this option to run the complete test set.""".dedent()
|
||||
),
|
||||
CmdOption.new(
|
||||
"-conf, --config",
|
||||
"-conf [testconfiguration.cfg]",
|
||||
"Run all tests by given test configuration. Default is 'GdUnitRunner.cfg'",
|
||||
TYPE_STRING,
|
||||
true
|
||||
),
|
||||
CmdOption.new(
|
||||
"-help", "",
|
||||
"Shows this help message."
|
||||
),
|
||||
CmdOption.new("--help-advanced", "",
|
||||
"Shows advanced options."
|
||||
)
|
||||
],
|
||||
[
|
||||
# advanced options
|
||||
CmdOption.new(
|
||||
"-rd, --report-directory",
|
||||
"-rd <directory>",
|
||||
"Specifies the output directory in which the reports are to be written. The default is res://reports/.",
|
||||
TYPE_STRING,
|
||||
true
|
||||
),
|
||||
CmdOption.new(
|
||||
"-rc, --report-count",
|
||||
"-rc <count>",
|
||||
"Specifies how many reports are saved before they are deleted. The default is %s." % str(DEFAULT_REPORT_COUNT),
|
||||
TYPE_INT,
|
||||
true
|
||||
),
|
||||
#CmdOption.new("--list-suites", "--list-suites [directory]", "Lists all test suites located in the given directory.", TYPE_STRING),
|
||||
#CmdOption.new("--describe-suite", "--describe-suite <suite name>", "Shows the description of selected test suite.", TYPE_STRING),
|
||||
CmdOption.new(
|
||||
"--info", "",
|
||||
"Shows the GdUnit version info"
|
||||
),
|
||||
CmdOption.new(
|
||||
"--selftest", "",
|
||||
"Runs the GdUnit self test"
|
||||
),
|
||||
CmdOption.new(
|
||||
"--ignoreHeadlessMode",
|
||||
"--ignoreHeadlessMode",
|
||||
"By default, running GdUnit4 in headless mode is not allowed. You can switch off the headless mode check by set this property."
|
||||
),
|
||||
])
|
||||
|
||||
|
||||
func _ready():
|
||||
_state = INIT
|
||||
_report_dir = GdUnitFileAccess.current_dir() + "reports"
|
||||
_executor = load("res://addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd").new()
|
||||
# stop checked first test failure to fail fast
|
||||
_executor.fail_fast(true)
|
||||
if GdUnit4CSharpApiLoader.is_mono_supported():
|
||||
prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version())
|
||||
_cs_executor = GdUnit4CSharpApiLoader.create_executor(self)
|
||||
var err := GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event)
|
||||
if err != OK:
|
||||
prints("gdUnitSignals failed")
|
||||
push_error("Error checked startup, can't connect executor for 'send_event'")
|
||||
quit(RETURN_ERROR)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
prints("Finallize .. done")
|
||||
|
||||
|
||||
func _process(_delta :float) -> void:
|
||||
match _state:
|
||||
INIT:
|
||||
init_gd_unit()
|
||||
_state = RUN
|
||||
RUN:
|
||||
# all test suites executed
|
||||
if _test_suites_to_process.is_empty():
|
||||
_state = STOP
|
||||
else:
|
||||
set_process(false)
|
||||
# process next test suite
|
||||
var test_suite := _test_suites_to_process.pop_front() as Node
|
||||
if _cs_executor != null and _cs_executor.IsExecutable(test_suite):
|
||||
_cs_executor.Execute(test_suite)
|
||||
await _cs_executor.ExecutionCompleted
|
||||
else:
|
||||
await _executor.execute(test_suite)
|
||||
set_process(true)
|
||||
STOP:
|
||||
_state = EXIT
|
||||
_on_gdunit_event(GdUnitStop.new())
|
||||
quit(report_exit_code(_report))
|
||||
|
||||
|
||||
func quit(code: int) -> void:
|
||||
_cs_executor = null
|
||||
GdUnitTools.dispose_all()
|
||||
await GdUnitMemoryObserver.gc_on_guarded_instances()
|
||||
await get_tree().physics_frame
|
||||
get_tree().quit(code)
|
||||
|
||||
|
||||
func set_report_dir(path: String) -> void:
|
||||
_report_dir = ProjectSettings.globalize_path(GdUnitFileAccess.make_qualified_path(path))
|
||||
_console.prints_color(
|
||||
"Set write reports to %s" % _report_dir,
|
||||
Color.DEEP_SKY_BLUE
|
||||
)
|
||||
|
||||
|
||||
func set_report_count(count: String) -> void:
|
||||
var report_count := count.to_int()
|
||||
if report_count < 1:
|
||||
_console.prints_error(
|
||||
"Invalid report history count '%s' set back to default %d"
|
||||
% [count, DEFAULT_REPORT_COUNT]
|
||||
)
|
||||
_report_max = DEFAULT_REPORT_COUNT
|
||||
else:
|
||||
_console.prints_color(
|
||||
"Set report history count to %s" % count,
|
||||
Color.DEEP_SKY_BLUE
|
||||
)
|
||||
_report_max = report_count
|
||||
|
||||
|
||||
func disable_fail_fast() -> void:
|
||||
_console.prints_color(
|
||||
"Disabled fail fast!",
|
||||
Color.DEEP_SKY_BLUE
|
||||
)
|
||||
_executor.fail_fast(false)
|
||||
|
||||
|
||||
func run_self_test() -> void:
|
||||
_console.prints_color(
|
||||
"Run GdUnit4 self tests.",
|
||||
Color.DEEP_SKY_BLUE
|
||||
)
|
||||
disable_fail_fast()
|
||||
_runner_config.self_test()
|
||||
|
||||
|
||||
func show_version() -> void:
|
||||
_console.prints_color(
|
||||
"Godot %s" % Engine.get_version_info().get("string"),
|
||||
Color.DARK_SALMON
|
||||
)
|
||||
var config := ConfigFile.new()
|
||||
config.load("addons/gdUnit4/plugin.cfg")
|
||||
_console.prints_color(
|
||||
"GdUnit4 %s" % config.get_value("plugin", "version"),
|
||||
Color.DARK_SALMON
|
||||
)
|
||||
quit(RETURN_SUCCESS)
|
||||
|
||||
|
||||
func check_headless_mode() -> void:
|
||||
_headless_mode_ignore = true
|
||||
|
||||
|
||||
func show_options(show_advanced: bool = false) -> void:
|
||||
_console.prints_color(
|
||||
"""
|
||||
Usage:
|
||||
runtest -a <directory|path of testsuite>
|
||||
runtest -a <directory> -i <path of testsuite|testsuite_name|testsuite_name:test_name>
|
||||
""".dedent(),
|
||||
Color.DARK_SALMON
|
||||
).prints_color(
|
||||
"-- Options ---------------------------------------------------------------------------------------",
|
||||
Color.DARK_SALMON
|
||||
).new_line()
|
||||
for option in _cmd_options.default_options():
|
||||
descripe_option(option)
|
||||
if show_advanced:
|
||||
_console.prints_color(
|
||||
"-- Advanced options --------------------------------------------------------------------------",
|
||||
Color.DARK_SALMON
|
||||
).new_line()
|
||||
for option in _cmd_options.advanced_options():
|
||||
descripe_option(option)
|
||||
|
||||
|
||||
func descripe_option(cmd_option: CmdOption) -> void:
|
||||
_console.print_color(
|
||||
" %-40s" % str(cmd_option.commands()),
|
||||
Color.CORNFLOWER_BLUE
|
||||
)
|
||||
_console.prints_color(
|
||||
cmd_option.description(),
|
||||
Color.LIGHT_GREEN
|
||||
)
|
||||
if not cmd_option.help().is_empty():
|
||||
_console.prints_color(
|
||||
"%-4s %s" % ["", cmd_option.help()],
|
||||
Color.DARK_TURQUOISE
|
||||
)
|
||||
_console.new_line()
|
||||
|
||||
|
||||
func load_test_config(path := GdUnitRunnerConfig.CONFIG_FILE) -> void:
|
||||
_console.print_color(
|
||||
"Loading test configuration %s\n" % path,
|
||||
Color.CORNFLOWER_BLUE
|
||||
)
|
||||
_runner_config.load_config(path)
|
||||
|
||||
|
||||
func show_help() -> void:
|
||||
show_options()
|
||||
quit(RETURN_SUCCESS)
|
||||
|
||||
|
||||
func show_advanced_help() -> void:
|
||||
show_options(true)
|
||||
quit(RETURN_SUCCESS)
|
||||
|
||||
|
||||
func init_gd_unit() -> void:
|
||||
_console.prints_color(
|
||||
"""
|
||||
--------------------------------------------------------------------------------------------------
|
||||
GdUnit4 Comandline Tool
|
||||
--------------------------------------------------------------------------------------------------""".dedent(),
|
||||
Color.DARK_SALMON
|
||||
).new_line()
|
||||
|
||||
var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd")
|
||||
var result := cmd_parser.parse(OS.get_cmdline_args())
|
||||
if result.is_error():
|
||||
show_options()
|
||||
_console.prints_error(result.error_message())
|
||||
_console.prints_error("Abnormal exit with %d" % RETURN_ERROR)
|
||||
_state = STOP
|
||||
quit(RETURN_ERROR)
|
||||
return
|
||||
if result.is_empty():
|
||||
show_help()
|
||||
return
|
||||
# build runner config by given commands
|
||||
result = (
|
||||
CmdCommandHandler.new(_cmd_options)
|
||||
.register_cb("-help", Callable(self, "show_help"))
|
||||
.register_cb("--help-advanced", Callable(self, "show_advanced_help"))
|
||||
.register_cb("-a", Callable(_runner_config, "add_test_suite"))
|
||||
.register_cbv("-a", Callable(_runner_config, "add_test_suites"))
|
||||
.register_cb("-i", Callable(_runner_config, "skip_test_suite"))
|
||||
.register_cbv("-i", Callable(_runner_config, "skip_test_suites"))
|
||||
.register_cb("-rd", set_report_dir)
|
||||
.register_cb("-rc", set_report_count)
|
||||
.register_cb("--selftest", run_self_test)
|
||||
.register_cb("-c", disable_fail_fast)
|
||||
.register_cb("-conf", load_test_config)
|
||||
.register_cb("--info", show_version)
|
||||
.register_cb("--ignoreHeadlessMode", check_headless_mode)
|
||||
.execute(result.value())
|
||||
)
|
||||
if result.is_error():
|
||||
_console.prints_error(result.error_message())
|
||||
_state = STOP
|
||||
quit(RETURN_ERROR)
|
||||
|
||||
if DisplayServer.get_name() == "headless":
|
||||
if _headless_mode_ignore:
|
||||
_console.prints_warning("""
|
||||
Headless mode is ignored by option '--ignoreHeadlessMode'"
|
||||
|
||||
Please note that tests that use UI interaction do not work correctly in headless mode.
|
||||
Godot 'InputEvents' are not transported by the Godot engine in headless mode and therefore
|
||||
have no effect in the test!
|
||||
""".dedent()
|
||||
).new_line()
|
||||
else:
|
||||
_console.prints_error("""
|
||||
Headless mode is not supported!
|
||||
|
||||
Please note that tests that use UI interaction do not work correctly in headless mode.
|
||||
Godot 'InputEvents' are not transported by the Godot engine in headless mode and therefore
|
||||
have no effect in the test!
|
||||
|
||||
You can run with '--ignoreHeadlessMode' to swtich off this check.
|
||||
""".dedent()
|
||||
).prints_error(
|
||||
"Abnormal exit with %d" % RETURN_ERROR_HEADLESS_NOT_SUPPORTED
|
||||
)
|
||||
quit(RETURN_ERROR_HEADLESS_NOT_SUPPORTED)
|
||||
return
|
||||
|
||||
_test_suites_to_process = load_testsuites(_runner_config)
|
||||
if _test_suites_to_process.is_empty():
|
||||
_console.prints_warning("No test suites found, abort test run!")
|
||||
_console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON)
|
||||
_state = STOP
|
||||
quit(RETURN_SUCCESS)
|
||||
var total_test_count := _collect_test_case_count(_test_suites_to_process)
|
||||
_on_gdunit_event(GdUnitInit.new(_test_suites_to_process.size(), total_test_count))
|
||||
|
||||
|
||||
func load_testsuites(config: GdUnitRunnerConfig) -> Array[Node]:
|
||||
var test_suites_to_process: Array[Node] = []
|
||||
var to_execute := config.to_execute()
|
||||
# scan for the requested test suites
|
||||
var ts_scanner := GdUnitTestSuiteScanner.new()
|
||||
for as_resource_path in to_execute.keys():
|
||||
var selected_tests: PackedStringArray = to_execute.get(as_resource_path)
|
||||
var scaned_suites := ts_scanner.scan(as_resource_path)
|
||||
skip_test_case(scaned_suites, selected_tests)
|
||||
test_suites_to_process.append_array(scaned_suites)
|
||||
skip_suites(test_suites_to_process, config)
|
||||
return test_suites_to_process
|
||||
|
||||
|
||||
func skip_test_case(test_suites: Array, test_case_names: Array) -> void:
|
||||
if test_case_names.is_empty():
|
||||
return
|
||||
for test_suite in test_suites:
|
||||
for test_case in test_suite.get_children():
|
||||
if not test_case_names.has(test_case.get_name()):
|
||||
test_suite.remove_child(test_case)
|
||||
test_case.free()
|
||||
|
||||
|
||||
func skip_suites(test_suites: Array, config: GdUnitRunnerConfig) -> void:
|
||||
var skipped := config.skipped()
|
||||
for test_suite in test_suites:
|
||||
skip_suite(test_suite, skipped)
|
||||
|
||||
|
||||
func skip_suite(test_suite: Node, skipped: Dictionary) -> void:
|
||||
var skipped_suites := skipped.keys()
|
||||
if skipped_suites.is_empty():
|
||||
return
|
||||
var suite_name := test_suite.get_name()
|
||||
# skipp c# testsuites for now
|
||||
if test_suite.get_script() == null:
|
||||
return
|
||||
var test_suite_path: String = (
|
||||
test_suite.get_meta("ResourcePath") if test_suite.get_script() == null
|
||||
else test_suite.get_script().resource_path
|
||||
)
|
||||
for suite_to_skip in skipped_suites:
|
||||
# if suite skipped by path or name
|
||||
if (
|
||||
suite_to_skip == test_suite_path
|
||||
or (suite_to_skip.is_valid_filename() and suite_to_skip == suite_name)
|
||||
):
|
||||
var skipped_tests: Array = skipped.get(suite_to_skip)
|
||||
# if no tests skipped test the complete suite is skipped
|
||||
if skipped_tests.is_empty():
|
||||
_console.prints_warning("Skip test suite %s:%s" % suite_to_skip)
|
||||
test_suite.skip(true)
|
||||
else:
|
||||
# skip tests
|
||||
for test_to_skip in skipped_tests:
|
||||
var test_case: _TestCase = test_suite.find_child(test_to_skip, true, false)
|
||||
if test_case:
|
||||
test_case.skip(true)
|
||||
_console.prints_warning("Skip test case %s:%s" % [suite_to_skip, test_to_skip])
|
||||
else:
|
||||
_console.prints_error(
|
||||
"Can't skip test '%s' checked test suite '%s', no test with given name exists!"
|
||||
% [test_to_skip, suite_to_skip]
|
||||
)
|
||||
|
||||
|
||||
func _collect_test_case_count(test_suites: Array) -> int:
|
||||
var total: int = 0
|
||||
for test_suite in test_suites:
|
||||
total += (test_suite as Node).get_child_count()
|
||||
return total
|
||||
|
||||
|
||||
# gdlint: disable=function-name
|
||||
func PublishEvent(data: Dictionary) -> void:
|
||||
_on_gdunit_event(GdUnitEvent.new().deserialize(data))
|
||||
|
||||
|
||||
func _on_gdunit_event(event: GdUnitEvent):
|
||||
match event.type():
|
||||
GdUnitEvent.INIT:
|
||||
_report = GdUnitHtmlReport.new(_report_dir)
|
||||
GdUnitEvent.STOP:
|
||||
if _report == null:
|
||||
_report = GdUnitHtmlReport.new(_report_dir)
|
||||
var report_path := _report.write()
|
||||
_report.delete_history(_report_max)
|
||||
JUnitXmlReport.new(_report._report_path, _report.iteration()).write(_report)
|
||||
_console.prints_color(
|
||||
"Total test suites: %s" % _report.suite_count(),
|
||||
Color.DARK_SALMON
|
||||
).prints_color(
|
||||
"Total test cases: %s" % _report.test_count(),
|
||||
Color.DARK_SALMON
|
||||
).prints_color(
|
||||
"Total time: %s" % LocalTime.elapsed(_report.duration()),
|
||||
Color.DARK_SALMON
|
||||
).prints_color(
|
||||
"Open Report at: file://%s" % report_path,
|
||||
Color.CORNFLOWER_BLUE
|
||||
)
|
||||
GdUnitEvent.TESTSUITE_BEFORE:
|
||||
_report.add_testsuite_report(
|
||||
GdUnitTestSuiteReport.new(event.resource_path(), event.suite_name())
|
||||
)
|
||||
GdUnitEvent.TESTSUITE_AFTER:
|
||||
_report.update_test_suite_report(
|
||||
event.resource_path(),
|
||||
event.elapsed_time(),
|
||||
event.is_error(),
|
||||
event.is_failed(),
|
||||
event.is_warning(),
|
||||
event.is_skipped(),
|
||||
event.skipped_count(),
|
||||
event.failed_count(),
|
||||
event.orphan_nodes(),
|
||||
event.reports()
|
||||
)
|
||||
GdUnitEvent.TESTCASE_BEFORE:
|
||||
_report.add_testcase_report(
|
||||
event.resource_path(),
|
||||
GdUnitTestCaseReport.new(
|
||||
event.resource_path(),
|
||||
event.suite_name(),
|
||||
event.test_name()
|
||||
)
|
||||
)
|
||||
GdUnitEvent.TESTCASE_AFTER:
|
||||
var test_report := GdUnitTestCaseReport.new(
|
||||
event.resource_path(),
|
||||
event.suite_name(),
|
||||
event.test_name(),
|
||||
event.is_error(),
|
||||
event.is_failed(),
|
||||
event.failed_count(),
|
||||
event.orphan_nodes(),
|
||||
event.is_skipped(),
|
||||
event.reports(),
|
||||
event.elapsed_time()
|
||||
)
|
||||
_report.update_testcase_report(event.resource_path(), test_report)
|
||||
print_status(event)
|
||||
|
||||
|
||||
func report_exit_code(report: GdUnitHtmlReport) -> int:
|
||||
if report.error_count() + report.failure_count() > 0:
|
||||
_console.prints_color("Exit code: %d" % RETURN_ERROR, Color.FIREBRICK)
|
||||
return RETURN_ERROR
|
||||
if report.orphan_count() > 0:
|
||||
_console.prints_color("Exit code: %d" % RETURN_WARNING, Color.GOLDENROD)
|
||||
return RETURN_WARNING
|
||||
_console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON)
|
||||
return RETURN_SUCCESS
|
||||
|
||||
|
||||
func print_status(event: GdUnitEvent) -> void:
|
||||
match event.type():
|
||||
GdUnitEvent.TESTSUITE_BEFORE:
|
||||
_console.prints_color(
|
||||
"Run Test Suite %s " % event.resource_path(),
|
||||
Color.ANTIQUE_WHITE
|
||||
)
|
||||
GdUnitEvent.TESTCASE_BEFORE:
|
||||
_console.print_color(
|
||||
" Run Test: %s > %s :" % [event.resource_path(), event.test_name()],
|
||||
Color.ANTIQUE_WHITE
|
||||
).prints_color(
|
||||
"STARTED",
|
||||
Color.FOREST_GREEN
|
||||
).save_cursor()
|
||||
GdUnitEvent.TESTCASE_AFTER:
|
||||
#_console.restore_cursor()
|
||||
_console.print_color(
|
||||
" Run Test: %s > %s :" % [event.resource_path(), event.test_name()],
|
||||
Color.ANTIQUE_WHITE
|
||||
)
|
||||
_print_status(event)
|
||||
_print_failure_report(event.reports())
|
||||
GdUnitEvent.TESTSUITE_AFTER:
|
||||
_print_failure_report(event.reports())
|
||||
_print_status(event)
|
||||
_console.prints_color(
|
||||
"Statistics: | %d tests cases | %d error | %d failed | %d skipped | %d orphans |\n"
|
||||
% [
|
||||
_report.test_count(),
|
||||
_report.error_count(),
|
||||
_report.failure_count(),
|
||||
_report.skipped_count(),
|
||||
_report.orphan_count()
|
||||
],
|
||||
Color.ANTIQUE_WHITE
|
||||
)
|
||||
|
||||
|
||||
func _print_failure_report(reports: Array) -> void:
|
||||
for report in reports:
|
||||
if (
|
||||
report.is_failure()
|
||||
or report.is_error()
|
||||
or report.is_warning()
|
||||
or report.is_skipped()
|
||||
):
|
||||
_console.prints_color(
|
||||
" Report:",
|
||||
Color.DARK_TURQUOISE, CmdConsole.BOLD | CmdConsole.UNDERLINE
|
||||
)
|
||||
var text = GdUnitTools.richtext_normalize(str(report))
|
||||
for line in text.split("\n"):
|
||||
_console.prints_color(" %s" % line, Color.DARK_TURQUOISE)
|
||||
_console.new_line()
|
||||
|
||||
|
||||
func _print_status(event: GdUnitEvent) -> void:
|
||||
if event.is_skipped():
|
||||
_console.print_color("SKIPPED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.ITALIC)
|
||||
elif event.is_failed() or event.is_error():
|
||||
_console.print_color("FAILED", Color.FIREBRICK, CmdConsole.BOLD)
|
||||
elif event.orphan_nodes() > 0:
|
||||
_console.print_color("PASSED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.UNDERLINE)
|
||||
else:
|
||||
_console.print_color("PASSED", Color.FOREST_GREEN, CmdConsole.BOLD)
|
||||
_console.prints_color(
|
||||
" %s" % LocalTime.elapsed(event.elapsed_time()), Color.CORNFLOWER_BLUE
|
||||
)
|
||||
|
||||
|
||||
var _cli_runner: CLIRunner
|
||||
|
||||
|
||||
func _initialize():
|
||||
if Engine.get_version_info().hex < 0x40100:
|
||||
prints("GdUnit4 requires a minimum of Godot 4.1.x Version!")
|
||||
quit(CLIRunner.RETURN_ERROR_GODOT_VERSION_NOT_SUPPORTED)
|
||||
return
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED)
|
||||
_cli_runner = CLIRunner.new()
|
||||
root.add_child(_cli_runner)
|
||||
|
||||
|
||||
# do not use print statements on _finalize it results in random crashes
|
||||
#func _finalize():
|
||||
# prints("Finallize ..")
|
||||
# prints("-Orphan nodes report-----------------------")
|
||||
# Window.print_orphan_nodes()
|
||||
# prints("Finallize .. done")
|
||||
141
addons/gdUnit4/bin/GdUnitCopyLog.gd
Normal file
141
addons/gdUnit4/bin/GdUnitCopyLog.gd
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#!/usr/bin/env -S godot -s
|
||||
extends MainLoop
|
||||
|
||||
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
|
||||
# gdlint: disable=max-line-length
|
||||
const NO_LOG_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<meta http-equiv="x-ua-compatible" content="IE=edge"/>
|
||||
<title>Logging</title>
|
||||
<link href="css/style.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>No logging available!</h1>
|
||||
</br>
|
||||
<p>For logging to occur, you must check Enable File Logging in Project Settings.</p>
|
||||
<p>You can enable Logging <b>Project Settings</b> > <b>Logging</b> > <b>File Logging</b> > <b>Enable File Logging</b> in the Project Settings.</p>
|
||||
</div>
|
||||
</body>
|
||||
"""
|
||||
|
||||
#warning-ignore-all:return_value_discarded
|
||||
var _cmd_options := CmdOptions.new([
|
||||
CmdOption.new(
|
||||
"-rd, --report-directory",
|
||||
"-rd <directory>",
|
||||
"Specifies the output directory in which the reports are to be written. The default is res://reports/.",
|
||||
TYPE_STRING,
|
||||
true
|
||||
)
|
||||
])
|
||||
|
||||
var _report_root_path: String
|
||||
|
||||
|
||||
func _init():
|
||||
_report_root_path = GdUnitFileAccess.current_dir() + "reports"
|
||||
|
||||
|
||||
func _process(_delta):
|
||||
# check if reports exists
|
||||
if not reports_available():
|
||||
prints("no reports found")
|
||||
return true
|
||||
# scan for latest report path
|
||||
var iteration := GdUnitFileAccess.find_last_path_index(
|
||||
_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX
|
||||
)
|
||||
var report_path = "%s/%s%d" % [_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX, iteration]
|
||||
# only process if godot logging is enabled
|
||||
if not GdUnitSettings.is_log_enabled():
|
||||
_patch_report(report_path, "")
|
||||
return true
|
||||
# parse possible custom report path,
|
||||
var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd")
|
||||
# ignore erros and exit quitly
|
||||
if cmd_parser.parse(OS.get_cmdline_args(), true).is_error():
|
||||
return true
|
||||
CmdCommandHandler.new(_cmd_options).register_cb("-rd", set_report_directory)
|
||||
# scan for latest godot log and copy to report
|
||||
var godot_log := _scan_latest_godot_log()
|
||||
var result := _copy_and_pach(godot_log, report_path)
|
||||
if result.is_error():
|
||||
push_error(result.error_message())
|
||||
return true
|
||||
_patch_report(report_path, godot_log)
|
||||
return true
|
||||
|
||||
|
||||
func set_report_directory(path: String) -> void:
|
||||
_report_root_path = path
|
||||
|
||||
|
||||
func _scan_latest_godot_log() -> String:
|
||||
var path := GdUnitSettings.get_log_path().get_base_dir()
|
||||
var files_sorted := Array()
|
||||
for file in GdUnitFileAccess.scan_dir(path):
|
||||
var file_name := "%s/%s" % [path, file]
|
||||
files_sorted.append(file_name)
|
||||
# sort by name, the name contains the timestamp so we sort at the end by timestamp
|
||||
files_sorted.sort()
|
||||
return files_sorted[-1]
|
||||
|
||||
|
||||
func _patch_report(report_path: String, godot_log: String) -> void:
|
||||
var index_file := FileAccess.open("%s/index.html" % report_path, FileAccess.READ_WRITE)
|
||||
if index_file == null:
|
||||
push_error(
|
||||
"Can't add log path to index.html. Error: %s"
|
||||
% error_string(FileAccess.get_open_error())
|
||||
)
|
||||
return
|
||||
# if no log file available than add a information howto enable it
|
||||
if godot_log.is_empty():
|
||||
FileAccess.open(
|
||||
"%s/logging_not_available.html" % report_path,
|
||||
FileAccess.WRITE).store_string(NO_LOG_TEMPLATE)
|
||||
var log_file = "logging_not_available.html" if godot_log.is_empty() else godot_log.get_file()
|
||||
var content := index_file.get_as_text().replace("${log_file}", log_file)
|
||||
# overide it
|
||||
index_file.seek(0)
|
||||
index_file.store_string(content)
|
||||
|
||||
|
||||
func _copy_and_pach(from_file: String, to_dir: String) -> GdUnitResult:
|
||||
var result := GdUnitFileAccess.copy_file(from_file, to_dir)
|
||||
if result.is_error():
|
||||
return result
|
||||
var file := FileAccess.open(from_file, FileAccess.READ)
|
||||
if file == null:
|
||||
return GdUnitResult.error(
|
||||
"Can't find file '%s'. Error: %s"
|
||||
% [from_file, error_string(FileAccess.get_open_error())]
|
||||
)
|
||||
var content := file.get_as_text()
|
||||
# patch out console format codes
|
||||
for color_index in range(0, 256):
|
||||
var to_replace := "[38;5;%dm" % color_index
|
||||
content = content.replace(to_replace, "")
|
||||
content = content\
|
||||
.replace("[0m", "")\
|
||||
.replace(CmdConsole.__CSI_BOLD, "")\
|
||||
.replace(CmdConsole.__CSI_ITALIC, "")\
|
||||
.replace(CmdConsole.__CSI_UNDERLINE, "")
|
||||
var to_file := to_dir + "/" + from_file.get_file()
|
||||
file = FileAccess.open(to_file, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return GdUnitResult.error(
|
||||
"Can't open to write '%s'. Error: %s"
|
||||
% [to_file, error_string(FileAccess.get_open_error())]
|
||||
)
|
||||
file.store_string(content)
|
||||
return GdUnitResult.empty()
|
||||
|
||||
|
||||
func reports_available() -> bool:
|
||||
return DirAccess.dir_exists_absolute(_report_root_path)
|
||||
99
addons/gdUnit4/bin/ProjectScanner.gd
Normal file
99
addons/gdUnit4/bin/ProjectScanner.gd
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env -S godot -s
|
||||
@tool
|
||||
extends SceneTree
|
||||
|
||||
const CmdConsole = preload("res://addons/gdUnit4/src/cmd/CmdConsole.gd")
|
||||
|
||||
|
||||
func _initialize():
|
||||
set_auto_accept_quit(false)
|
||||
var scanner := SourceScanner.new(self)
|
||||
root.add_child(scanner)
|
||||
|
||||
|
||||
# gdlint: disable=trailing-whitespace
|
||||
class SourceScanner extends Node:
|
||||
|
||||
enum {
|
||||
INIT,
|
||||
STARTUP,
|
||||
SCAN,
|
||||
QUIT,
|
||||
DONE
|
||||
}
|
||||
|
||||
var _state = INIT
|
||||
var _console := CmdConsole.new()
|
||||
var _elapsed_time := 0.0
|
||||
var _plugin: EditorPlugin
|
||||
var _fs: EditorFileSystem
|
||||
var _scene: SceneTree
|
||||
|
||||
|
||||
func _init(scene :SceneTree) -> void:
|
||||
_scene = scene
|
||||
_console.prints_color("""
|
||||
========================================================================
|
||||
Running project scan:""".dedent(),
|
||||
Color.CORNFLOWER_BLUE
|
||||
)
|
||||
_state = INIT
|
||||
|
||||
|
||||
func _process(delta :float) -> void:
|
||||
_elapsed_time += delta
|
||||
set_process(false)
|
||||
await_inital_scan()
|
||||
await scan_project()
|
||||
set_process(true)
|
||||
|
||||
|
||||
# !! don't use any await in this phase otherwise the editor will be instable !!
|
||||
func await_inital_scan() -> void:
|
||||
if _state == INIT:
|
||||
_console.prints_color("Wait initial scanning ...", Color.DARK_GREEN)
|
||||
_plugin = EditorPlugin.new()
|
||||
_fs = _plugin.get_editor_interface().get_resource_filesystem()
|
||||
_plugin.get_editor_interface().set_plugin_enabled("gdUnit4", false)
|
||||
_state = STARTUP
|
||||
|
||||
if _state == STARTUP:
|
||||
if _fs.is_scanning():
|
||||
_console.progressBar(_fs.get_scanning_progress() * 100 as int)
|
||||
# we wait 10s in addition to be on the save site the scanning is done
|
||||
if _elapsed_time > 10.0:
|
||||
_console.progressBar(100)
|
||||
_console.new_line()
|
||||
_console.prints_color("initial scanning ... done", Color.DARK_GREEN)
|
||||
_state = SCAN
|
||||
|
||||
|
||||
func scan_project() -> void:
|
||||
if _state != SCAN:
|
||||
return
|
||||
_console.prints_color("Scan project: ", Color.SANDY_BROWN)
|
||||
await get_tree().process_frame
|
||||
_fs.scan_sources()
|
||||
await get_tree().create_timer(5).timeout
|
||||
_console.prints_color("Scan: ", Color.SANDY_BROWN)
|
||||
_console.progressBar(0)
|
||||
await get_tree().process_frame
|
||||
_fs.scan()
|
||||
while _fs.is_scanning():
|
||||
await get_tree().process_frame
|
||||
_console.progressBar(_fs.get_scanning_progress() * 100 as int)
|
||||
await get_tree().create_timer(10).timeout
|
||||
_console.progressBar(100)
|
||||
_console.new_line()
|
||||
_plugin.free()
|
||||
_console.prints_color("""
|
||||
Scan project done.
|
||||
========================================================================""".dedent(),
|
||||
Color.CORNFLOWER_BLUE
|
||||
)
|
||||
await get_tree().process_frame
|
||||
await get_tree().physics_frame
|
||||
queue_free()
|
||||
# force quit editor
|
||||
_state = DONE
|
||||
_scene.quit(0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue