Compare commits
2 Commits
b6d402eb93
...
2783f107c9
Author | SHA1 | Date |
---|---|---|
Martin Felis | 2783f107c9 | |
Martin Felis | a521cf4e96 |
|
@ -1 +0,0 @@
|
|||
{"included":{"res://tests/scenes/game_tests.gd":["test_save_game"]},"server_port":31002,"skipped":{},"version":"1.0"}
|
|
@ -68,7 +68,7 @@ func _idle(_delta :float) -> void:
|
|||
exit(RETURN_ERROR, result.error_message())
|
||||
return
|
||||
_console.prints_color("Added testcase: %s" % result.value(), Color.CORNFLOWER_BLUE)
|
||||
print_json_result(result.value())
|
||||
print_json_result(result.value() as Dictionary)
|
||||
exit(RETURN_SUCCESS)
|
||||
|
||||
|
||||
|
@ -85,7 +85,7 @@ func exit(code :int, message :String = "") -> void:
|
|||
|
||||
func print_json_result(result :Dictionary) -> void:
|
||||
# convert back to system path
|
||||
var path := ProjectSettings.globalize_path(result["path"]);
|
||||
var path := ProjectSettings.globalize_path(result["path"] as String)
|
||||
var json := 'JSON_RESULT:{"TestCases" : [{"line":%d, "path": "%s"}]}' % [result["line"], path]
|
||||
prints(json)
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ class CLIRunner:
|
|||
var _headless_mode_ignore := false
|
||||
var _runner_config := GdUnitRunnerConfig.new()
|
||||
var _runner_config_file := ""
|
||||
var _debug_cmd_args: = PackedStringArray()
|
||||
var _console := CmdConsole.new()
|
||||
var _cmd_options := CmdOptions.new([
|
||||
CmdOption.new(
|
||||
|
@ -105,9 +106,10 @@ class CLIRunner:
|
|||
func _ready() -> void:
|
||||
_state = INIT
|
||||
_report_dir = GdUnitFileAccess.current_dir() + "reports"
|
||||
_executor = load("res://addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd").new()
|
||||
_executor = GdUnitTestSuiteExecutor.new()
|
||||
# stop checked first test failure to fail fast
|
||||
_executor.fail_fast(true)
|
||||
@warning_ignore("unsafe_cast")
|
||||
(_executor as GdUnitTestSuiteExecutor).fail_fast(true)
|
||||
if GdUnit4CSharpApiLoader.is_mono_supported():
|
||||
prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version())
|
||||
_cs_executor = GdUnit4CSharpApiLoader.create_executor(self)
|
||||
|
@ -123,6 +125,7 @@ class CLIRunner:
|
|||
prints("Finallize .. done")
|
||||
|
||||
|
||||
@warning_ignore("unsafe_method_access")
|
||||
func _process(_delta :float) -> void:
|
||||
match _state:
|
||||
INIT:
|
||||
|
@ -135,7 +138,8 @@ class CLIRunner:
|
|||
else:
|
||||
set_process(false)
|
||||
# process next test suite
|
||||
var test_suite := _test_suites_to_process.pop_front() as Node
|
||||
var test_suite: Node = _test_suites_to_process.pop_front()
|
||||
|
||||
if _cs_executor != null and _cs_executor.IsExecutable(test_suite):
|
||||
_cs_executor.Execute(test_suite)
|
||||
await _cs_executor.ExecutionCompleted
|
||||
|
@ -185,6 +189,7 @@ class CLIRunner:
|
|||
"Disabled fail fast!",
|
||||
Color.DEEP_SKY_BLUE
|
||||
)
|
||||
@warning_ignore("unsafe_method_access")
|
||||
_executor.fail_fast(false)
|
||||
|
||||
|
||||
|
@ -199,13 +204,13 @@ class CLIRunner:
|
|||
|
||||
func show_version() -> void:
|
||||
_console.prints_color(
|
||||
"Godot %s" % Engine.get_version_info().get("string"),
|
||||
"Godot %s" % Engine.get_version_info().get("string") as String,
|
||||
Color.DARK_SALMON
|
||||
)
|
||||
var config := ConfigFile.new()
|
||||
config.load("addons/gdUnit4/plugin.cfg")
|
||||
_console.prints_color(
|
||||
"GdUnit4 %s" % config.get_value("plugin", "version"),
|
||||
"GdUnit4 %s" % config.get_value("plugin", "version") as String,
|
||||
Color.DARK_SALMON
|
||||
)
|
||||
quit(RETURN_SUCCESS)
|
||||
|
@ -274,6 +279,12 @@ class CLIRunner:
|
|||
quit(RETURN_SUCCESS)
|
||||
|
||||
|
||||
func get_cmdline_args() -> PackedStringArray:
|
||||
if _debug_cmd_args.is_empty():
|
||||
return OS.get_cmdline_args()
|
||||
return _debug_cmd_args
|
||||
|
||||
|
||||
func init_gd_unit() -> void:
|
||||
_console.prints_color(
|
||||
"""
|
||||
|
@ -284,7 +295,7 @@ class CLIRunner:
|
|||
).new_line()
|
||||
|
||||
var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd")
|
||||
var result := cmd_parser.parse(OS.get_cmdline_args())
|
||||
var result := cmd_parser.parse(get_cmdline_args())
|
||||
if result.is_error():
|
||||
show_options()
|
||||
_console.prints_error(result.error_message())
|
||||
|
@ -297,7 +308,8 @@ class CLIRunner:
|
|||
return
|
||||
# build runner config by given commands
|
||||
var commands :Array[CmdCommand] = []
|
||||
commands.append_array(result.value())
|
||||
@warning_ignore("unsafe_cast")
|
||||
commands.append_array(result.value() as Array)
|
||||
result = (
|
||||
CmdCommandHandler.new(_cmd_options)
|
||||
.register_cb("-help", Callable(self, "show_help"))
|
||||
|
@ -385,7 +397,7 @@ class CLIRunner:
|
|||
var skipped := config.skipped()
|
||||
if skipped.is_empty():
|
||||
return
|
||||
_console.prints_warning("Found excluded test suite's configured at '%s'" % _runner_config_file)
|
||||
|
||||
for test_suite in test_suites:
|
||||
# skipp c# testsuites for now
|
||||
if test_suite.get_script() == null:
|
||||
|
@ -395,24 +407,26 @@ class CLIRunner:
|
|||
|
||||
# Dictionary[String, PackedStringArray]
|
||||
func skip_suite(test_suite: Node, skipped: Dictionary) -> void:
|
||||
var skipped_suites :Array[String] = skipped.keys()
|
||||
var skipped_suites :Array = skipped.keys()
|
||||
var suite_name := test_suite.get_name()
|
||||
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:
|
||||
for suite_to_skip: String 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[String] = skipped.get(suite_to_skip)
|
||||
var skip_reason := "Excluded by config '%s'" % _runner_config_file
|
||||
var skipped_tests: PackedStringArray = skipped.get(suite_to_skip)
|
||||
var skip_reason := "Excluded by configuration"
|
||||
# if no tests skipped test the complete suite is skipped
|
||||
if skipped_tests.is_empty():
|
||||
_console.prints_warning("Mark test suite '%s' as skipped!" % suite_to_skip)
|
||||
_console.prints_warning("Mark the entire test suite '%s' as skipped!" % test_suite_path)
|
||||
@warning_ignore("unsafe_property_access")
|
||||
test_suite.__is_skipped = true
|
||||
@warning_ignore("unsafe_property_access")
|
||||
test_suite.__skip_reason = skip_reason
|
||||
else:
|
||||
# skip tests
|
||||
|
@ -443,10 +457,8 @@ class CLIRunner:
|
|||
func _on_gdunit_event(event: GdUnitEvent) -> void:
|
||||
match event.type():
|
||||
GdUnitEvent.INIT:
|
||||
_report = GdUnitHtmlReport.new(_report_dir)
|
||||
_report = GdUnitHtmlReport.new(_report_dir, _report_max)
|
||||
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)
|
||||
|
@ -464,45 +476,31 @@ class CLIRunner:
|
|||
Color.CORNFLOWER_BLUE
|
||||
)
|
||||
GdUnitEvent.TESTSUITE_BEFORE:
|
||||
_report.add_testsuite_report(
|
||||
GdUnitTestSuiteReport.new(event.resource_path(), event.suite_name(), event.total_count())
|
||||
)
|
||||
_report.add_testsuite_report(event.resource_path(), event.suite_name(), event.total_count())
|
||||
GdUnitEvent.TESTSUITE_AFTER:
|
||||
_report.update_test_suite_report(
|
||||
_report.add_testsuite_reports(
|
||||
event.resource_path(),
|
||||
event.elapsed_time(),
|
||||
event.is_error(),
|
||||
event.is_failed(),
|
||||
event.is_warning(),
|
||||
event.is_skipped(),
|
||||
event.skipped_count(),
|
||||
event.error_count(),
|
||||
event.failed_count(),
|
||||
event.orphan_nodes(),
|
||||
event.elapsed_time(),
|
||||
event.reports()
|
||||
)
|
||||
GdUnitEvent.TESTCASE_BEFORE:
|
||||
_report.add_testcase_report(
|
||||
event.resource_path(),
|
||||
GdUnitTestCaseReport.new(
|
||||
event.resource_path(),
|
||||
event.suite_name(),
|
||||
event.test_name()
|
||||
)
|
||||
)
|
||||
_report.add_testcase(event.resource_path(), event.suite_name(), event.test_name())
|
||||
GdUnitEvent.TESTCASE_AFTER:
|
||||
var test_report := GdUnitTestCaseReport.new(
|
||||
event.resource_path(),
|
||||
event.suite_name(),
|
||||
_report.set_testcase_counters(event.resource_path(),
|
||||
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)
|
||||
event.is_flaky(),
|
||||
event.elapsed_time())
|
||||
_report.add_testcase_reports(event.resource_path(), event.test_name(), event.reports())
|
||||
GdUnitEvent.TESTCASE_STATISTICS:
|
||||
_report.update_testsuite_counters(event.resource_path(), event.is_error(), event.failed_count(), event.orphan_nodes(),\
|
||||
event.is_skipped(), event.is_flaky(), event.elapsed_time())
|
||||
print_status(event)
|
||||
|
||||
|
||||
|
@ -556,11 +554,12 @@ class CLIRunner:
|
|||
_print_failure_report(event.reports())
|
||||
_print_status(event)
|
||||
_console.prints_color(
|
||||
"Statistics: | %d tests cases | %d error | %d failed | %d skipped | %d orphans |\n"
|
||||
"Statistics: | %d tests cases | %d error | %d failed | %d flaky | %d skipped | %d orphans |\n"
|
||||
% [
|
||||
_report.test_count(),
|
||||
_report.error_count(),
|
||||
_report.failure_count(),
|
||||
_report.flaky_count(),
|
||||
_report.skipped_count(),
|
||||
_report.orphan_count()
|
||||
],
|
||||
|
@ -587,14 +586,22 @@ class CLIRunner:
|
|||
|
||||
|
||||
func _print_status(event: GdUnitEvent) -> void:
|
||||
if event.is_skipped():
|
||||
if event.is_flaky() and event.is_success():
|
||||
var retries :int = event.statistic(GdUnitEvent.RETRY_COUNT)
|
||||
_console.print_color("FLAKY (%d retries)" % retries, Color.GREEN_YELLOW, CmdConsole.BOLD | CmdConsole.ITALIC)
|
||||
elif event.is_success():
|
||||
_console.print_color("PASSED", Color.FOREST_GREEN, CmdConsole.BOLD)
|
||||
elif 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)
|
||||
var retries :int = event.statistic(GdUnitEvent.RETRY_COUNT)
|
||||
if retries > 1:
|
||||
_console.print_color("FAILED (retry %d)" % retries, Color.FIREBRICK, CmdConsole.BOLD)
|
||||
else:
|
||||
_console.print_color("FAILED", Color.FIREBRICK, CmdConsole.BOLD)
|
||||
elif event.is_warning():
|
||||
_console.print_color("WARNING", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.UNDERLINE)
|
||||
|
||||
_console.prints_color(
|
||||
" %s" % LocalTime.elapsed(event.elapsed_time()), Color.CORNFLOWER_BLUE
|
||||
)
|
||||
|
@ -615,6 +622,7 @@ func _initialize() -> void:
|
|||
|
||||
# do not use print statements on _finalize it results in random crashes
|
||||
func _finalize() -> void:
|
||||
queue_delete(_cli_runner)
|
||||
if OS.is_stdout_verbose():
|
||||
prints("Finallize ..")
|
||||
prints("-Orphan nodes report-----------------------")
|
||||
|
|
|
@ -4,23 +4,30 @@ extends MainLoop
|
|||
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
|
||||
# gdlint: disable=max-line-length
|
||||
const NO_LOG_TEMPLATE = """
|
||||
const LOG_FRAME_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html style="display: inline-grid;">
|
||||
<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"/>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Godot Logging</title>
|
||||
<link rel="stylesheet" href="css/styles.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>
|
||||
|
||||
<body style="background-color: #eee;">
|
||||
<div class="godot-report-frame"">
|
||||
${content}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
const NO_LOG_MESSAGE = """
|
||||
<h3>No logging available!</h3>
|
||||
</br>
|
||||
<p>In order for logging to take place, you must activate the Activate file logging option in the project settings.</p>
|
||||
<p>You can enable the logging under:
|
||||
<b>Project Settings</b> > <b>Debug</b> > <b>File Logging</b> > <b>Enable File Logging</b> in the project settings.</p>
|
||||
"""
|
||||
|
||||
#warning-ignore-all:return_value_discarded
|
||||
|
@ -34,48 +41,65 @@ var _cmd_options := CmdOptions.new([
|
|||
)
|
||||
])
|
||||
|
||||
|
||||
var _report_root_path: String
|
||||
var _current_report_path: String
|
||||
var _debug_cmd_args := PackedStringArray()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_report_root_path = GdUnitFileAccess.current_dir() + "reports"
|
||||
set_report_directory(GdUnitFileAccess.current_dir() + "reports")
|
||||
set_current_report_path()
|
||||
|
||||
|
||||
func _process(_delta :float) -> bool:
|
||||
func _process(_delta: float) -> bool:
|
||||
# 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, "")
|
||||
write_report(NO_LOG_MESSAGE, "")
|
||||
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():
|
||||
if cmd_parser.parse(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)
|
||||
|
||||
var godot_log_file := scan_latest_godot_log()
|
||||
var result := read_log_file_content(godot_log_file)
|
||||
if result.is_error():
|
||||
push_error(result.error_message())
|
||||
write_report(result.error_message(), godot_log_file)
|
||||
return true
|
||||
_patch_report(report_path, godot_log)
|
||||
write_report(result.value_as_string(), godot_log_file)
|
||||
return true
|
||||
|
||||
|
||||
func set_current_report_path() -> void:
|
||||
# scan for latest report directory
|
||||
var iteration := GdUnitFileAccess.find_last_path_index(
|
||||
_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX
|
||||
)
|
||||
_current_report_path = "%s/%s%d" % [_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX, iteration]
|
||||
|
||||
|
||||
func set_report_directory(path: String) -> void:
|
||||
_report_root_path = path
|
||||
|
||||
|
||||
func _scan_latest_godot_log() -> String:
|
||||
func get_log_report_html() -> String:
|
||||
return _current_report_path + "/godot_report_log.html"
|
||||
|
||||
|
||||
func reports_available() -> bool:
|
||||
return DirAccess.dir_exists_absolute(_report_root_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):
|
||||
|
@ -83,59 +107,60 @@ func _scan_latest_godot_log() -> String:
|
|||
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]
|
||||
return files_sorted.back()
|
||||
|
||||
|
||||
func _patch_report(report_path: String, godot_log: String) -> void:
|
||||
var index_file := FileAccess.open("%s/index.html" % report_path, FileAccess.READ_WRITE)
|
||||
func read_log_file_content(log_file: String) -> GdUnitResult:
|
||||
var file := FileAccess.open(log_file, FileAccess.READ)
|
||||
if file == null:
|
||||
return GdUnitResult.error(
|
||||
"Can't find log file '%s'. Error: %s"
|
||||
% [log_file, error_string(FileAccess.get_open_error())]
|
||||
)
|
||||
var content := "<pre>" + 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 += "</pre>"
|
||||
content = content\
|
||||
.replace("[0m", "")\
|
||||
.replace(CmdConsole.CSI_BOLD, "")\
|
||||
.replace(CmdConsole.CSI_ITALIC, "")\
|
||||
.replace(CmdConsole.CSI_UNDERLINE, "")
|
||||
return GdUnitResult.success(content)
|
||||
|
||||
|
||||
func write_report(content: String, godot_log_file: String) -> GdUnitResult:
|
||||
var file := FileAccess.open(get_log_report_html(), FileAccess.WRITE)
|
||||
if file == null:
|
||||
return GdUnitResult.error(
|
||||
"Can't open to write '%s'. Error: %s"
|
||||
% [get_log_report_html(), error_string(FileAccess.get_open_error())]
|
||||
)
|
||||
var report_html := LOG_FRAME_TEMPLATE.replace("${content}", content)
|
||||
file.store_string(report_html)
|
||||
_update_index_html(godot_log_file)
|
||||
return GdUnitResult.success(file)
|
||||
|
||||
|
||||
func _update_index_html(godot_log_file: String) -> void:
|
||||
var index_file := FileAccess.open("%s/index.html" % _current_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)
|
||||
var content := index_file.get_as_text()\
|
||||
.replace("${log_report}", get_log_report_html())\
|
||||
.replace("${godot_log_file}", godot_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)
|
||||
func get_cmdline_args() -> PackedStringArray:
|
||||
if _debug_cmd_args.is_empty():
|
||||
return OS.get_cmdline_args()
|
||||
return _debug_cmd_args
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
#!/usr/bin/env -S godot -s
|
||||
@tool
|
||||
extends SceneTree
|
||||
|
||||
const CmdConsole = preload("res://addons/gdUnit4/src/cmd/CmdConsole.gd")
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
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)
|
|
@ -3,5 +3,5 @@
|
|||
name="gdUnit4"
|
||||
description="Unit Testing Framework for Godot Scripts"
|
||||
author="Mike Schulze"
|
||||
version="4.3.1"
|
||||
version="4.4.3"
|
||||
script="plugin.gd"
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
const GdUnitTestDiscoverGuard := preload("res://addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd")
|
||||
const GdUnitTools := preload ("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
const GdUnitTestDiscoverGuard := preload ("res://addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd")
|
||||
|
||||
|
||||
var _gd_inspector :Node
|
||||
var _server_node :Node
|
||||
var _gd_console :Node
|
||||
var _gd_inspector: Control
|
||||
var _gd_console: Control
|
||||
var _guard: GdUnitTestDiscoverGuard
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
if check_running_in_test_env():
|
||||
@warning_ignore("return_value_discarded")
|
||||
CmdConsole.new().prints_warning("It was recognized that GdUnit4 is running in a test environment, therefore the GdUnit4 plugin will not be executed!")
|
||||
return
|
||||
if Engine.get_version_info().hex < 0x40200:
|
||||
prints("GdUnit4 plugin requires a minimum of Godot 4.2.x Version!")
|
||||
return
|
||||
|
@ -21,34 +24,39 @@ func _enter_tree() -> void:
|
|||
add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_UR, _gd_inspector)
|
||||
# install the GdUnit Console
|
||||
_gd_console = load("res://addons/gdUnit4/src/ui/GdUnitConsole.tscn").instantiate()
|
||||
@warning_ignore("return_value_discarded")
|
||||
add_control_to_bottom_panel(_gd_console, "gdUnitConsole")
|
||||
_server_node = load("res://addons/gdUnit4/src/network/GdUnitServer.tscn").instantiate()
|
||||
Engine.get_main_loop().root.add_child.call_deferred(_server_node)
|
||||
prints("Loading GdUnit4 Plugin success")
|
||||
if GdUnitSettings.is_update_notification_enabled():
|
||||
var update_tool :Node = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate()
|
||||
var update_tool: Node = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate()
|
||||
Engine.get_main_loop().root.add_child.call_deferred(update_tool)
|
||||
if GdUnit4CSharpApiLoader.is_mono_supported():
|
||||
prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version())
|
||||
# connect to be notified for script changes to be able to discover new tests
|
||||
_guard = GdUnitTestDiscoverGuard.new()
|
||||
@warning_ignore("return_value_discarded")
|
||||
resource_saved.connect(_on_resource_saved)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if check_running_in_test_env():
|
||||
return
|
||||
if is_instance_valid(_gd_inspector):
|
||||
remove_control_from_docks(_gd_inspector)
|
||||
GodotVersionFixures.free_fix(_gd_inspector)
|
||||
_gd_inspector.free()
|
||||
if is_instance_valid(_gd_console):
|
||||
remove_control_from_bottom_panel(_gd_console)
|
||||
_gd_console.free()
|
||||
if is_instance_valid(_server_node):
|
||||
Engine.get_main_loop().root.remove_child.call_deferred(_server_node)
|
||||
_server_node.queue_free()
|
||||
GdUnitTools.dispose_all.call_deferred()
|
||||
GdUnitTools.dispose_all(true)
|
||||
prints("Unload GdUnit4 Plugin success")
|
||||
|
||||
|
||||
func _on_resource_saved(resource :Resource) -> void:
|
||||
func check_running_in_test_env() -> bool:
|
||||
var args := OS.get_cmdline_args()
|
||||
args.append_array(OS.get_cmdline_user_args())
|
||||
return DisplayServer.get_name() == "headless" or args.has("--selftest") or args.has("--add") or args.has("-a") or args.has("--quit-after") or args.has("--import")
|
||||
|
||||
|
||||
func _on_resource_saved(resource: Resource) -> void:
|
||||
if resource is Script:
|
||||
_guard.discover(resource)
|
||||
await _guard.discover(resource as Script)
|
||||
|
|
|
@ -158,3 +158,14 @@ func extractv(
|
|||
extractor8 :GdUnitValueExtractor = null,
|
||||
extractor9 :GdUnitValueExtractor = null) -> GdUnitArrayAssert:
|
||||
return self
|
||||
|
||||
|
||||
|
||||
@warning_ignore("unused_parameter")
|
||||
func override_failure_message(message :String) -> GdUnitArrayAssert:
|
||||
return self
|
||||
|
||||
|
||||
@warning_ignore("unused_parameter")
|
||||
func append_failure_message(message :String) -> GdUnitArrayAssert:
|
||||
return self
|
||||
|
|
|
@ -18,19 +18,19 @@ func is_not_null():
|
|||
## Verifies that the current value is equal to expected one.
|
||||
@warning_ignore("unused_parameter")
|
||||
@warning_ignore("untyped_declaration")
|
||||
func is_equal(expected):
|
||||
func is_equal(expected: Variant):
|
||||
return self
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@warning_ignore("unused_parameter")
|
||||
@warning_ignore("untyped_declaration")
|
||||
func is_not_equal(expected):
|
||||
func is_not_equal(expected: Variant):
|
||||
return self
|
||||
|
||||
|
||||
@warning_ignore("untyped_declaration")
|
||||
func test_fail():
|
||||
func do_fail():
|
||||
return self
|
||||
|
||||
|
||||
|
@ -39,3 +39,11 @@ func test_fail():
|
|||
@warning_ignore("untyped_declaration")
|
||||
func override_failure_message(message :String):
|
||||
return self
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
## This can be used to add additional infromations to the generated failure message.
|
||||
@warning_ignore("unused_parameter")
|
||||
@warning_ignore("untyped_declaration")
|
||||
func append_failure_message(message :String):
|
||||
return self
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
class_name GdUnitAwaiter
|
||||
extends RefCounted
|
||||
|
||||
const GdUnitAssertImpl = preload("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd")
|
||||
|
||||
|
||||
# Waits for a specified signal in an interval of 50ms sent from the <source>, and terminates with an error after the specified timeout has elapsed.
|
||||
# source: the object from which the signal is emitted
|
||||
|
@ -14,16 +12,19 @@ func await_signal_on(source :Object, signal_name :String, args :Array = [], time
|
|||
var assert_that := GdUnitAssertImpl.new(signal_name)
|
||||
var line_number := GdUnitAssertions.get_line_number()
|
||||
if not is_instance_valid(source):
|
||||
@warning_ignore("return_value_discarded")
|
||||
assert_that.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number)
|
||||
return await Engine.get_main_loop().process_frame
|
||||
return await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
# fail fast if the given source instance invalid
|
||||
if not is_instance_valid(source):
|
||||
@warning_ignore("return_value_discarded")
|
||||
assert_that.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number)
|
||||
return await await_idle_frame()
|
||||
var awaiter := GdUnitSignalAwaiter.new(timeout_millis)
|
||||
var value :Variant = await awaiter.on_signal(source, signal_name, args)
|
||||
if awaiter.is_interrupted():
|
||||
var failure := "await_signal_on(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis]
|
||||
@warning_ignore("return_value_discarded")
|
||||
assert_that.report_error(failure, line_number)
|
||||
return value
|
||||
|
||||
|
@ -37,6 +38,7 @@ func await_signal_idle_frames(source :Object, signal_name :String, args :Array =
|
|||
var line_number := GdUnitAssertions.get_line_number()
|
||||
# fail fast if the given source instance invalid
|
||||
if not is_instance_valid(source):
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitAssertImpl.new(signal_name)\
|
||||
.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number)
|
||||
return await await_idle_frame()
|
||||
|
@ -44,6 +46,7 @@ func await_signal_idle_frames(source :Object, signal_name :String, args :Array =
|
|||
var value :Variant = await awaiter.on_signal(source, signal_name, args)
|
||||
if awaiter.is_interrupted():
|
||||
var failure := "await_signal_idle_frames(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis]
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitAssertImpl.new(signal_name).report_error(failure, line_number)
|
||||
return value
|
||||
|
||||
|
@ -56,7 +59,7 @@ func await_signal_idle_frames(source :Object, signal_name :String, args :Array =
|
|||
func await_millis(milliSec :int) -> void:
|
||||
var timer :Timer = Timer.new()
|
||||
timer.set_name("gdunit_await_millis_timer_%d" % timer.get_instance_id())
|
||||
Engine.get_main_loop().root.add_child(timer)
|
||||
(Engine.get_main_loop() as SceneTree).root.add_child(timer)
|
||||
timer.add_to_group("GdUnitTimers")
|
||||
timer.set_one_shot(true)
|
||||
timer.start(milliSec / 1000.0)
|
||||
|
@ -66,4 +69,4 @@ func await_millis(milliSec :int) -> void:
|
|||
|
||||
# Waits until the next idle frame
|
||||
func await_idle_frame() -> void:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
|
|
|
@ -29,3 +29,9 @@ func has_message(expected: String) -> GdUnitFailureAssert:
|
|||
@warning_ignore("unused_parameter")
|
||||
func starts_with_message(expected: String) -> GdUnitFailureAssert:
|
||||
return self
|
||||
|
||||
|
||||
## Verifies that the failure message contains the expected message.
|
||||
@warning_ignore("unused_parameter")
|
||||
func contains_message(expected: String) -> GdUnitFailureAssert:
|
||||
return self
|
||||
|
|
|
@ -3,15 +3,15 @@ class_name GdUnitFloatAssert
|
|||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to expected one.
|
||||
## Verifies that the current String is equal to the given one.
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_equal(expected :float) -> GdUnitFloatAssert:
|
||||
func is_equal(expected :Variant) -> GdUnitFloatAssert:
|
||||
return self
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
## Verifies that the current String is not equal to the given one.
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_not_equal(expected :float) -> GdUnitFloatAssert:
|
||||
func is_not_equal(expected :Variant) -> GdUnitFloatAssert:
|
||||
return self
|
||||
|
||||
|
||||
|
|
|
@ -5,39 +5,39 @@ extends GdUnitAssert
|
|||
|
||||
## Verifies that the current value is null.
|
||||
func is_null() -> GdUnitFuncAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
func is_not_null() -> GdUnitFuncAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_equal(expected :Variant) -> GdUnitFuncAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to the given one.
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_not_equal(expected :Variant) -> GdUnitFuncAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Verifies that the current value is true.
|
||||
func is_true() -> GdUnitFuncAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Verifies that the current value is false.
|
||||
func is_false() -> GdUnitFuncAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ extends GdUnitAssert
|
|||
## await assert_error(<callable>).is_success()
|
||||
## [/codeblock]
|
||||
func is_success() -> GdUnitGodotErrorAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ func is_success() -> GdUnitGodotErrorAssert:
|
|||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_runtime_error(expected_error :String) -> GdUnitGodotErrorAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
|
@ -31,7 +31,7 @@ func is_runtime_error(expected_error :String) -> GdUnitGodotErrorAssert:
|
|||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_push_warning(expected_warning :String) -> GdUnitGodotErrorAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
|
@ -42,5 +42,5 @@ func is_push_warning(expected_warning :String) -> GdUnitGodotErrorAssert:
|
|||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_push_error(expected_error :String) -> GdUnitGodotErrorAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
|
|
@ -2,16 +2,15 @@
|
|||
class_name GdUnitIntAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to expected one.
|
||||
## Verifies that the current String is equal to the given one.
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_equal(expected :int) -> GdUnitIntAssert:
|
||||
func is_equal(expected :Variant) -> GdUnitIntAssert:
|
||||
return self
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
## Verifies that the current String is not equal to the given one.
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_not_equal(expected :int) -> GdUnitIntAssert:
|
||||
func is_not_equal(expected :Variant) -> GdUnitIntAssert:
|
||||
return self
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,81 @@
|
|||
## The scene runner for GdUnit to simmulate scene interactions
|
||||
## The Scene Runner is a tool used for simulating interactions on a scene.
|
||||
## With this tool, you can simulate input events such as keyboard or mouse input and/or simulate scene processing over a certain number of frames.
|
||||
## This tool is typically used for integration testing a scene.
|
||||
class_name GdUnitSceneRunner
|
||||
extends RefCounted
|
||||
|
||||
const NO_ARG = GdUnitConstants.NO_ARG
|
||||
|
||||
|
||||
## Sets the mouse cursor to given position relative to the viewport.
|
||||
## Simulates that an action has been pressed.[br]
|
||||
## [member action] : the action e.g. [code]"ui_up"[/code][br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func set_mouse_pos(pos :Vector2) -> GdUnitSceneRunner:
|
||||
func simulate_action_pressed(action: String) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Gets the current mouse position of the current viewport
|
||||
## Simulates that an action is pressed.[br]
|
||||
## [member action] : the action e.g. [code]"ui_up"[/code][br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_action_press(action: String) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates that an action has been released.[br]
|
||||
## [member action] : the action e.g. [code]"ui_up"[/code][br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_action_release(action: String) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates that a key has been pressed.[br]
|
||||
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
|
||||
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
|
||||
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
|
||||
## [codeblock]
|
||||
## func test_key_presssed():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## await runner.simulate_key_pressed(KEY_SPACE)
|
||||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Simulates that a key is pressed.[br]
|
||||
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
|
||||
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
|
||||
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates that a key has been released.[br]
|
||||
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
|
||||
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
|
||||
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Sets the mouse cursor to given position relative to the viewport.
|
||||
## @deprecated: Use [set_mouse_position] instead.
|
||||
@warning_ignore("unused_parameter")
|
||||
func set_mouse_pos(position: Vector2) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Sets the mouse position to the specified vector, provided in pixels and relative to an origin at the upper left corner of the currently focused Window Manager game window.[br]
|
||||
## [member position] : The absolute position in pixels as Vector2
|
||||
@warning_ignore("unused_parameter")
|
||||
func set_mouse_position(position: Vector2) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Returns the mouse's position in this Viewport using the coordinate system of this Viewport.
|
||||
func get_mouse_position() -> Vector2:
|
||||
return Vector2.ZERO
|
||||
|
||||
|
@ -21,58 +85,10 @@ func get_global_mouse_position() -> Vector2:
|
|||
return Vector2.ZERO
|
||||
|
||||
|
||||
## Simulates that an action has been pressed.[br]
|
||||
## [member action] : the action e.g. [code]"ui_up"[/code][br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_action_pressed(action :String) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates that an action is pressed.[br]
|
||||
## [member action] : the action e.g. [code]"ui_up"[/code][br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_action_press(action :String) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates that an action has been released.[br]
|
||||
## [member action] : the action e.g. [code]"ui_up"[/code][br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_action_release(action :String) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates that a key has been pressed.[br]
|
||||
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
|
||||
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
|
||||
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_key_pressed(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates that a key is pressed.[br]
|
||||
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
|
||||
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
|
||||
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates that a key has been released.[br]
|
||||
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
|
||||
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
|
||||
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_key_release(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a mouse moved to final position.[br]
|
||||
## [member pos] : The final mouse position
|
||||
## [member position] : The final mouse position
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner:
|
||||
func simulate_mouse_move(position: Vector2) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
|
@ -89,7 +105,7 @@ func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner:
|
|||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
|
@ -106,36 +122,149 @@ func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_ty
|
|||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a mouse button pressed.[br]
|
||||
## [member buttonIndex] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
|
||||
## [member button_index] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
|
||||
## [member double_click] : Set to true to simulate a double-click
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_mouse_button_pressed(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner:
|
||||
func simulate_mouse_button_pressed(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a mouse button press (holding)[br]
|
||||
## [member buttonIndex] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
|
||||
## [member button_index] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
|
||||
## [member double_click] : Set to true to simulate a double-click
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_mouse_button_press(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner:
|
||||
func simulate_mouse_button_press(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a mouse button released.[br]
|
||||
## [member buttonIndex] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
|
||||
## [member button_index] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_mouse_button_release(buttonIndex :MouseButton) -> GdUnitSceneRunner:
|
||||
func simulate_mouse_button_release(button_index: MouseButton) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a screen touch is pressed.[br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member position] : The position to touch the screen.[br]
|
||||
## [member double_tap] : If true, the touch's state is a double tab.
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_screen_touch_pressed(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a screen touch press without releasing it immediately, effectively simulating a "hold" action.[br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member position] : The position to touch the screen.[br]
|
||||
## [member double_tap] : If true, the touch's state is a double tab.
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_screen_touch_press(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a screen touch is released.[br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member double_tap] : If true, the touch's state is a double tab.
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_screen_touch_release(index: int, double_tap := false) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a touch drag and drop event to a relative position.[br]
|
||||
## [color=yellow]You must use [b]await[/b] to wait until the simulated drag&drop is complete.[/color][br]
|
||||
## [br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member relative] : The relative position, indicating the drag&drop position offset.[br]
|
||||
## [member time] : The time to move to the relative position in seconds (default is 1 second).[br]
|
||||
## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br]
|
||||
## [codeblock]
|
||||
## func test_touch_drag_drop():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## # start drag at position 50,50
|
||||
## runner.simulate_screen_touch_drag_begin(1, Vector2(50, 50))
|
||||
## # and drop it at final at 150,50 relative (50,50 + 100,0)
|
||||
## await runner.simulate_screen_touch_drag_relative(1, Vector2(100,0))
|
||||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_screen_touch_drag_relative(index: int, relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a touch screen drop to the absolute coordinates (offset).[br]
|
||||
## [color=yellow]You must use [b]await[/b] to wait until the simulated drop is complete.[/color][br]
|
||||
## [br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member position] : The final position, indicating the drop position.[br]
|
||||
## [member time] : The time to move to the final position in seconds (default is 1 second).[br]
|
||||
## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br]
|
||||
## [codeblock]
|
||||
## func test_touch_drag_drop():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## # start drag at position 50,50
|
||||
## runner.simulate_screen_touch_drag_begin(1, Vector2(50, 50))
|
||||
## # and drop it at 100,50
|
||||
## await runner.simulate_screen_touch_drag_absolute(1, Vector2(100,50))
|
||||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_screen_touch_drag_absolute(index: int, position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a complete drag and drop event from one position to another.[br]
|
||||
## This is ideal for testing complex drag-and-drop scenarios that require a specific start and end position.[br]
|
||||
## [color=yellow]You must use [b]await[/b] to wait until the simulated drop is complete.[/color][br]
|
||||
## [br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member position] : The drag start position, indicating the drag position.[br]
|
||||
## [member drop_position] : The drop position, indicating the drop position.[br]
|
||||
## [member time] : The time to move to the final position in seconds (default is 1 second).[br]
|
||||
## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br]
|
||||
## [codeblock]
|
||||
## func test_touch_drag_drop():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## # start drag at position 50,50 and drop it at 100,50
|
||||
## await runner.simulate_screen_touch_drag_drop(1, Vector2(50, 50), Vector2(100,50))
|
||||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_screen_touch_drag_drop(index: int, position: Vector2, drop_position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Simulates a touch screen drag event to given position.[br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member position] : The drag start position, indicating the drag position.[br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_screen_touch_drag(index: int, position: Vector2) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Returns the actual position of the touchscreen drag position by given index.
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func get_screen_touch_drag_position(index: int) -> Vector2:
|
||||
return Vector2.ZERO
|
||||
|
||||
|
||||
## Sets how fast or slow the scene simulation is processed (clock ticks versus the real).[br]
|
||||
## It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life,
|
||||
## whilst a value of 0.5 means the game moves at half the regular speed.
|
||||
|
||||
|
||||
## Sets the time factor for the scene simulation.
|
||||
## [member time_factor] : A float representing the simulation speed.[br]
|
||||
## - Default is 1.0, meaning the simulation runs at normal speed.[br]
|
||||
## - A value of 2.0 means the simulation runs twice as fast as real time.[br]
|
||||
## - A value of 0.5 means the simulation runs at half the regular speed.[br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func set_time_factor(time_factor := 1.0) -> GdUnitSceneRunner:
|
||||
func set_time_factor(time_factor: float = 1.0) -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
|
@ -143,8 +272,8 @@ func set_time_factor(time_factor := 1.0) -> GdUnitSceneRunner:
|
|||
## [member frames] : amount of frames to process[br]
|
||||
## [member delta_milli] : the time delta between a frame in milliseconds
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner:
|
||||
await Engine.get_main_loop().process_frame
|
||||
func simulate_frames(frames: int, delta_milli: int = -1) -> GdUnitSceneRunner:
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
|
@ -153,18 +282,18 @@ func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner:
|
|||
## [member args] : optional signal arguments to be matched for stop[br]
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_until_signal(
|
||||
signal_name :String,
|
||||
arg0 :Variant = NO_ARG,
|
||||
arg1 :Variant = NO_ARG,
|
||||
arg2 :Variant = NO_ARG,
|
||||
arg3 :Variant = NO_ARG,
|
||||
arg4 :Variant = NO_ARG,
|
||||
arg5 :Variant = NO_ARG,
|
||||
arg6 :Variant = NO_ARG,
|
||||
arg7 :Variant = NO_ARG,
|
||||
arg8 :Variant = NO_ARG,
|
||||
arg9 :Variant = NO_ARG) -> GdUnitSceneRunner:
|
||||
await Engine.get_main_loop().process_frame
|
||||
signal_name: String,
|
||||
arg0: Variant = NO_ARG,
|
||||
arg1: Variant = NO_ARG,
|
||||
arg2: Variant = NO_ARG,
|
||||
arg3: Variant = NO_ARG,
|
||||
arg4: Variant = NO_ARG,
|
||||
arg5: Variant = NO_ARG,
|
||||
arg6: Variant = NO_ARG,
|
||||
arg7: Variant = NO_ARG,
|
||||
arg8: Variant = NO_ARG,
|
||||
arg9: Variant = NO_ARG) -> GdUnitSceneRunner:
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
|
@ -174,64 +303,103 @@ func simulate_until_signal(
|
|||
## [member args] : optional signal arguments to be matched for stop
|
||||
@warning_ignore("unused_parameter")
|
||||
func simulate_until_object_signal(
|
||||
source :Object,
|
||||
signal_name :String,
|
||||
arg0 :Variant = NO_ARG,
|
||||
arg1 :Variant = NO_ARG,
|
||||
arg2 :Variant = NO_ARG,
|
||||
arg3 :Variant = NO_ARG,
|
||||
arg4 :Variant = NO_ARG,
|
||||
arg5 :Variant = NO_ARG,
|
||||
arg6 :Variant = NO_ARG,
|
||||
arg7 :Variant = NO_ARG,
|
||||
arg8 :Variant = NO_ARG,
|
||||
arg9 :Variant = NO_ARG) -> GdUnitSceneRunner:
|
||||
await Engine.get_main_loop().process_frame
|
||||
source: Object,
|
||||
signal_name: String,
|
||||
arg0: Variant = NO_ARG,
|
||||
arg1: Variant = NO_ARG,
|
||||
arg2: Variant = NO_ARG,
|
||||
arg3: Variant = NO_ARG,
|
||||
arg4: Variant = NO_ARG,
|
||||
arg5: Variant = NO_ARG,
|
||||
arg6: Variant = NO_ARG,
|
||||
arg7: Variant = NO_ARG,
|
||||
arg8: Variant = NO_ARG,
|
||||
arg9: Variant = NO_ARG) -> GdUnitSceneRunner:
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
### Waits for all input events are processed
|
||||
## Waits for all input events to be processed by flushing any buffered input events
|
||||
## and then awaiting a full cycle of both the process and physics frames.[br]
|
||||
## [br]
|
||||
## This is typically used to ensure that any simulated or queued inputs are fully
|
||||
## processed before proceeding with the next steps in the scene.[br]
|
||||
## It's essential for reliable input simulation or when synchronizing logic based
|
||||
## on inputs.[br]
|
||||
##
|
||||
## Usage Example:
|
||||
## [codeblock]
|
||||
## await await_input_processed() # Ensure all inputs are processed before continuing
|
||||
## [/codeblock]
|
||||
func await_input_processed() -> void:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await Engine.get_main_loop().physics_frame
|
||||
if scene() != null and scene().process_mode != Node.PROCESS_MODE_DISABLED:
|
||||
Input.flush_buffered_events()
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).physics_frame
|
||||
|
||||
|
||||
## Waits for the function return value until specified timeout or fails.[br]
|
||||
## [member args] : optional function arguments
|
||||
## The await_func function pauses execution until a specified function in the scene returns a value.[br]
|
||||
## It returns a [GdUnitFuncAssert], which provides a suite of assertion methods to verify the returned value.[br]
|
||||
## [member func_name] : The name of the function to wait for.[br]
|
||||
## [member args] : Optional function arguments
|
||||
## [br]
|
||||
## Usage Example:
|
||||
## [codeblock]
|
||||
## # Waits for 'calculate_score' function and verifies the result is equal to 100.
|
||||
## await_func("calculate_score").is_equal(100)
|
||||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func await_func(func_name :String, args := []) -> GdUnitFuncAssert:
|
||||
func await_func(func_name: String, args := []) -> GdUnitFuncAssert:
|
||||
return null
|
||||
|
||||
|
||||
## Waits for the function return value of specified source until specified timeout or fails.[br]
|
||||
## [member source : the object where implements the function[br]
|
||||
|
||||
## The await_func_on function extends the functionality of await_func by allowing you to specify a source node within the scene.[br]
|
||||
## It waits for a specified function on that node to return a value and returns a [GdUnitFuncAssert] object for assertions.[br]
|
||||
## [member source] : The object where implements the function.[br]
|
||||
## [member func_name] : The name of the function to wait for.[br]
|
||||
## [member args] : optional function arguments
|
||||
## [br]
|
||||
## Usage Example:
|
||||
## [codeblock]
|
||||
## # Waits for 'calculate_score' function and verifies the result is equal to 100.
|
||||
## var my_instance := ScoreCalculator.new()
|
||||
## await_func(my_instance, "calculate_score").is_equal(100)
|
||||
## [/codeblock]
|
||||
@warning_ignore("unused_parameter")
|
||||
func await_func_on(source :Object, func_name :String, args := []) -> GdUnitFuncAssert:
|
||||
func await_func_on(source: Object, func_name: String, args := []) -> GdUnitFuncAssert:
|
||||
return null
|
||||
|
||||
|
||||
## Waits for given signal is emited by the scene until a specified timeout to fail.[br]
|
||||
## [member signal_name] : signal name[br]
|
||||
## [member args] : the expected signal arguments as an array[br]
|
||||
## [member timeout] : the timeout in ms, default is set to 2000ms
|
||||
## Waits for the specified signal to be emitted by the scene. If the signal is not emitted within the given timeout, the operation fails.[br]
|
||||
## [member signal_name] : The name of the signal to wait for[br]
|
||||
## [member args] : The signal arguments as an array[br]
|
||||
## [member timeout] : The maximum duration (in milliseconds) to wait for the signal to be emitted before failing
|
||||
@warning_ignore("unused_parameter")
|
||||
func await_signal(signal_name :String, args := [], timeout := 2000 ) -> void:
|
||||
await Engine.get_main_loop().process_frame
|
||||
func await_signal(signal_name: String, args := [], timeout := 2000 ) -> void:
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
pass
|
||||
|
||||
|
||||
## Waits for given signal is emited by the <source> until a specified timeout to fail.[br]
|
||||
## Waits for the specified signal to be emitted by a particular source node. If the signal is not emitted within the given timeout, the operation fails.[br]
|
||||
## [member source] : the object from which the signal is emitted[br]
|
||||
## [member signal_name] : signal name[br]
|
||||
## [member args] : the expected signal arguments as an array[br]
|
||||
## [member timeout] : the timeout in ms, default is set to 2000ms
|
||||
## [member signal_name] : The name of the signal to wait for[br]
|
||||
## [member args] : The signal arguments as an array[br]
|
||||
## [member timeout] : tThe maximum duration (in milliseconds) to wait for the signal to be emitted before failing
|
||||
@warning_ignore("unused_parameter")
|
||||
func await_signal_on(source :Object, signal_name :String, args := [], timeout := 2000 ) -> void:
|
||||
func await_signal_on(source: Object, signal_name: String, args := [], timeout := 2000 ) -> void:
|
||||
pass
|
||||
|
||||
|
||||
## maximizes the window to bring the scene visible
|
||||
## Restores the scene window to a windowed mode and brings it to the foreground.[br]
|
||||
## This ensures that the scene is visible and active during testing, making it easier to observe and interact with.
|
||||
func move_window_to_foreground() -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
||||
## Restores the scene window to a windowed mode and brings it to the foreground.[br]
|
||||
## This ensures that the scene is visible and active during testing, making it easier to observe and interact with.
|
||||
## @deprecated: Use [move_window_to_foreground] instead.
|
||||
func maximize_view() -> GdUnitSceneRunner:
|
||||
return self
|
||||
|
||||
|
@ -240,7 +408,7 @@ func maximize_view() -> GdUnitSceneRunner:
|
|||
## [member name] : name of property[br]
|
||||
## [member return] : the value of the property
|
||||
@warning_ignore("unused_parameter")
|
||||
func get_property(name :String) -> Variant:
|
||||
func get_property(name: String) -> Variant:
|
||||
return null
|
||||
|
||||
## Set the value <value> of the property with the name <name>.[br]
|
||||
|
@ -248,7 +416,7 @@ func get_property(name :String) -> Variant:
|
|||
## [member value] : value of property[br]
|
||||
## [member return] : true|false depending on valid property name.
|
||||
@warning_ignore("unused_parameter")
|
||||
func set_property(name :String, value :Variant) -> bool:
|
||||
func set_property(name: String, value: Variant) -> bool:
|
||||
return false
|
||||
|
||||
|
||||
|
@ -258,17 +426,17 @@ func set_property(name :String, value :Variant) -> bool:
|
|||
## [member return] : the function result
|
||||
@warning_ignore("unused_parameter")
|
||||
func invoke(
|
||||
name :String,
|
||||
arg0 :Variant = NO_ARG,
|
||||
arg1 :Variant = NO_ARG,
|
||||
arg2 :Variant = NO_ARG,
|
||||
arg3 :Variant = NO_ARG,
|
||||
arg4 :Variant = NO_ARG,
|
||||
arg5 :Variant = NO_ARG,
|
||||
arg6 :Variant = NO_ARG,
|
||||
arg7 :Variant = NO_ARG,
|
||||
arg8 :Variant = NO_ARG,
|
||||
arg9 :Variant = NO_ARG) -> Variant:
|
||||
name: String,
|
||||
arg0: Variant = NO_ARG,
|
||||
arg1: Variant = NO_ARG,
|
||||
arg2: Variant = NO_ARG,
|
||||
arg3: Variant = NO_ARG,
|
||||
arg4: Variant = NO_ARG,
|
||||
arg5: Variant = NO_ARG,
|
||||
arg6: Variant = NO_ARG,
|
||||
arg7: Variant = NO_ARG,
|
||||
arg8: Variant = NO_ARG,
|
||||
arg9: Variant = NO_ARG) -> Variant:
|
||||
return null
|
||||
|
||||
|
||||
|
@ -277,7 +445,7 @@ func invoke(
|
|||
## [member recursive] : enables/disables seraching recursive[br]
|
||||
## [member return] : the node if find otherwise null
|
||||
@warning_ignore("unused_parameter")
|
||||
func find_child(name :String, recursive :bool = true, owned :bool = false) -> Node:
|
||||
func find_child(name: String, recursive: bool = true, owned: bool = false) -> Node:
|
||||
return null
|
||||
|
||||
|
||||
|
|
|
@ -6,14 +6,14 @@ extends GdUnitAssert
|
|||
## Verifies that given signal is emitted until waiting time
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_emitted(name :String, args := []) -> GdUnitSignalAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
## Verifies that given signal is NOT emitted until waiting time
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return self
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
## [/codeblock]
|
||||
## @tutorial: https://mikeschulze.github.io/gdUnit4/faq/test-suite/
|
||||
|
||||
@icon("res://addons/gdUnit4/src/ui/assets/TestSuite.svg")
|
||||
@icon("res://addons/gdUnit4/src/ui/settings/logo.png")
|
||||
class_name GdUnitTestSuite
|
||||
extends Node
|
||||
|
||||
|
@ -23,8 +23,6 @@ var __is_skipped := false
|
|||
var __skip_reason :String = "Unknow."
|
||||
var __active_test_case :String
|
||||
var __awaiter := __gdunit_awaiter()
|
||||
# holds the actual execution context
|
||||
var __execution_context :RefCounted
|
||||
|
||||
|
||||
### We now load all used asserts and tool scripts into the cache according to the principle of "lazy loading"
|
||||
|
@ -100,25 +98,25 @@ func error_as_string(error_number :int) -> String:
|
|||
|
||||
## A litle helper to auto freeing your created objects after test execution
|
||||
func auto_free(obj :Variant) -> Variant:
|
||||
if __execution_context != null:
|
||||
return __execution_context.register_auto_free(obj)
|
||||
else:
|
||||
if is_instance_valid(obj):
|
||||
obj.queue_free()
|
||||
return obj
|
||||
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
|
||||
|
||||
assert(execution_context != null, "INTERNAL ERROR: The current execution_context is null! Please report this as bug.")
|
||||
return execution_context.register_auto_free(obj)
|
||||
|
||||
|
||||
@warning_ignore("native_method_override")
|
||||
func add_child(node :Node, force_readable_name := false, internal := Node.INTERNAL_MODE_DISABLED) -> void:
|
||||
super.add_child(node, force_readable_name, internal)
|
||||
if __execution_context != null:
|
||||
__execution_context.orphan_monitor_start()
|
||||
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
|
||||
if execution_context != null:
|
||||
execution_context.orphan_monitor_start()
|
||||
|
||||
|
||||
## Discard the error message triggered by a timeout (interruption).[br]
|
||||
## By default, an interrupted test is reported as an error.[br]
|
||||
## This function allows you to change the message to Success when an interrupted error is reported.
|
||||
func discard_error_interupted_by_timeout() -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__gdunit_tools().register_expect_interupted_by_timeout(self, __active_test_case)
|
||||
|
||||
|
||||
|
@ -126,12 +124,14 @@ func discard_error_interupted_by_timeout() -> void:
|
|||
## Useful for storing data during test execution. [br]
|
||||
## The directory is automatically deleted after test suite execution
|
||||
func create_temp_dir(relative_path :String) -> String:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_file_access().create_temp_dir(relative_path)
|
||||
|
||||
|
||||
## Deletes the temporary base directory[br]
|
||||
## Is called automatically after each execution of the test suite
|
||||
func clean_temp_dir() -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__gdunit_file_access().clear_tmp()
|
||||
|
||||
|
||||
|
@ -139,28 +139,26 @@ func clean_temp_dir() -> void:
|
|||
## with given name <file_name> and given file <mode> (default = File.WRITE)[br]
|
||||
## If success the returned File is automatically closed after the execution of the test suite
|
||||
func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_file_access().create_temp_file(relative_path, file_name, mode)
|
||||
|
||||
|
||||
## Reads a resource by given path <resource_path> into a PackedStringArray.
|
||||
func resource_as_array(resource_path :String) -> PackedStringArray:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_file_access().resource_as_array(resource_path)
|
||||
|
||||
|
||||
## Reads a resource by given path <resource_path> and returned the content as String.
|
||||
func resource_as_string(resource_path :String) -> String:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_file_access().resource_as_string(resource_path)
|
||||
|
||||
|
||||
## Reads a resource by given path <resource_path> and return Variand translated by str_to_var
|
||||
func resource_as_var(resource_path :String) -> Variant:
|
||||
return str_to_var(__gdunit_file_access().resource_as_string(resource_path))
|
||||
|
||||
|
||||
## clears the debuger error list[br]
|
||||
## PROTOTYPE!!!! Don't use it for now
|
||||
func clear_push_errors() -> void:
|
||||
__gdunit_tools().clear_push_errors()
|
||||
@warning_ignore("unsafe_method_access", "unsafe_cast")
|
||||
return str_to_var(__gdunit_file_access().resource_as_string(resource_path) as String)
|
||||
|
||||
|
||||
## Waits for given signal is emited by the <source> until a specified timeout to fail[br]
|
||||
|
@ -169,11 +167,13 @@ func clear_push_errors() -> void:
|
|||
## args: the expected signal arguments as an array[br]
|
||||
## timeout: the timeout in ms, default is set to 2000ms
|
||||
func await_signal_on(source :Object, signal_name :String, args :Array = [], timeout :int = 2000) -> Variant:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return await __awaiter.await_signal_on(source, signal_name, args, timeout)
|
||||
|
||||
|
||||
## Waits until the next idle frame
|
||||
func await_idle_frame() -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
await __awaiter.await_idle_frame()
|
||||
|
||||
|
||||
|
@ -185,6 +185,7 @@ func await_idle_frame() -> void:
|
|||
## [/codeblock][br]
|
||||
## use this waiter and not `await get_tree().create_timer().timeout to prevent errors when a test case is timed out
|
||||
func await_millis(timeout :int) -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
await __awaiter.await_millis(timeout)
|
||||
|
||||
|
||||
|
@ -216,11 +217,13 @@ const RETURN_DEEP_STUB = GdUnitMock.RETURN_DEEP_STUB
|
|||
|
||||
## Creates a mock for given class name
|
||||
func mock(clazz :Variant, mock_mode := RETURN_DEFAULTS) -> Variant:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __lazy_load("res://addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd").build(clazz, mock_mode)
|
||||
|
||||
|
||||
## Creates a spy checked given object instance
|
||||
func spy(instance :Variant) -> Variant:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __lazy_load("res://addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd").build(instance)
|
||||
|
||||
|
||||
|
@ -236,21 +239,25 @@ func do_return(value :Variant) -> GdUnitMock:
|
|||
|
||||
## Verifies certain behavior happened at least once or exact number of times
|
||||
func verify(obj :Variant, times := 1) -> Variant:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_object_interactions().verify(obj, times)
|
||||
|
||||
|
||||
## Verifies no interactions is happen checked this mock or spy
|
||||
func verify_no_interactions(obj :Variant) -> GdUnitAssert:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_object_interactions().verify_no_interactions(obj)
|
||||
|
||||
|
||||
## Verifies the given mock or spy has any unverified interaction.
|
||||
func verify_no_more_interactions(obj :Variant) -> GdUnitAssert:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_object_interactions().verify_no_more_interactions(obj)
|
||||
|
||||
|
||||
## Resets the saved function call counters checked a mock or spy
|
||||
func reset(obj :Variant) -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__gdunit_object_interactions().reset(obj)
|
||||
|
||||
|
||||
|
@ -267,6 +274,7 @@ func reset(obj :Variant) -> void:
|
|||
## await assert_signal(emitter).is_emitted('my_signal')
|
||||
## [/codeblock]
|
||||
func monitor_signals(source :Object, _auto_free := true) -> Object:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__lazy_load("res://addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd")\
|
||||
.get_current_context()\
|
||||
.get_signal_collector()\
|
||||
|
@ -277,36 +285,43 @@ func monitor_signals(source :Object, _auto_free := true) -> Object:
|
|||
# === Argument matchers ========================================================
|
||||
## Argument matcher to match any argument
|
||||
func any() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().any()
|
||||
|
||||
|
||||
## Argument matcher to match any boolean value
|
||||
func any_bool() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_BOOL)
|
||||
|
||||
|
||||
## Argument matcher to match any integer value
|
||||
func any_int() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_INT)
|
||||
|
||||
|
||||
## Argument matcher to match any float value
|
||||
func any_float() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_FLOAT)
|
||||
|
||||
|
||||
## Argument matcher to match any string value
|
||||
func any_string() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_STRING)
|
||||
|
||||
|
||||
## Argument matcher to match any Color value
|
||||
func any_color() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_COLOR)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector typed value
|
||||
func any_vector() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_types([
|
||||
TYPE_VECTOR2,
|
||||
TYPE_VECTOR2I,
|
||||
|
@ -319,141 +334,169 @@ func any_vector() -> GdUnitArgumentMatcher:
|
|||
|
||||
## Argument matcher to match any Vector2 value
|
||||
func any_vector2() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR2)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector2i value
|
||||
func any_vector2i() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR2I)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector3 value
|
||||
func any_vector3() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR3)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector3i value
|
||||
func any_vector3i() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR3I)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector4 value
|
||||
func any_vector4() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR4)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector3i value
|
||||
func any_vector4i() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR4I)
|
||||
|
||||
|
||||
## Argument matcher to match any Rect2 value
|
||||
func any_rect2() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_RECT2)
|
||||
|
||||
|
||||
## Argument matcher to match any Plane value
|
||||
func any_plane() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PLANE)
|
||||
|
||||
|
||||
## Argument matcher to match any Quaternion value
|
||||
func any_quat() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_QUATERNION)
|
||||
|
||||
|
||||
## Argument matcher to match any AABB value
|
||||
func any_aabb() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_AABB)
|
||||
|
||||
|
||||
## Argument matcher to match any Basis value
|
||||
func any_basis() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_BASIS)
|
||||
|
||||
|
||||
## Argument matcher to match any Transform2D value
|
||||
func any_transform_2d() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM2D)
|
||||
|
||||
|
||||
## Argument matcher to match any Transform3D value
|
||||
func any_transform_3d() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM3D)
|
||||
|
||||
|
||||
## Argument matcher to match any NodePath value
|
||||
func any_node_path() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_NODE_PATH)
|
||||
|
||||
|
||||
## Argument matcher to match any RID value
|
||||
func any_rid() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_RID)
|
||||
|
||||
|
||||
## Argument matcher to match any Object value
|
||||
func any_object() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_OBJECT)
|
||||
|
||||
|
||||
## Argument matcher to match any Dictionary value
|
||||
func any_dictionary() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_DICTIONARY)
|
||||
|
||||
|
||||
## Argument matcher to match any Array value
|
||||
func any_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedByteArray value
|
||||
func any_packed_byte_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_BYTE_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedInt32Array value
|
||||
func any_packed_int32_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT32_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedInt64Array value
|
||||
func any_packed_int64_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT64_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedFloat32Array value
|
||||
func any_packed_float32_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT32_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedFloat64Array value
|
||||
func any_packed_float64_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT64_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedStringArray value
|
||||
func any_packed_string_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_STRING_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedVector2Array value
|
||||
func any_packed_vector2_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR2_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedVector3Array value
|
||||
func any_packed_vector3_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR3_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedColorArray value
|
||||
func any_packed_color_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_COLOR_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any instance of given class
|
||||
func any_class(clazz :Object) -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().any_class(clazz)
|
||||
|
||||
|
||||
|
@ -492,13 +535,13 @@ func assert_that(current :Variant) -> GdUnitAssert:
|
|||
TYPE_STRING:
|
||||
return assert_str(current)
|
||||
TYPE_VECTOR2, TYPE_VECTOR2I, TYPE_VECTOR3, TYPE_VECTOR3I, TYPE_VECTOR4, TYPE_VECTOR4I:
|
||||
return assert_vector(current)
|
||||
return assert_vector(current, false)
|
||||
TYPE_DICTIONARY:
|
||||
return assert_dict(current)
|
||||
TYPE_ARRAY, TYPE_PACKED_BYTE_ARRAY, TYPE_PACKED_INT32_ARRAY, TYPE_PACKED_INT64_ARRAY,\
|
||||
TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY, TYPE_PACKED_STRING_ARRAY,\
|
||||
TYPE_PACKED_VECTOR2_ARRAY, TYPE_PACKED_VECTOR3_ARRAY, TYPE_PACKED_COLOR_ARRAY:
|
||||
return assert_array(current)
|
||||
return assert_array(current, false)
|
||||
TYPE_OBJECT, TYPE_NIL:
|
||||
return assert_object(current)
|
||||
_:
|
||||
|
@ -531,13 +574,13 @@ func assert_float(current :Variant) -> GdUnitFloatAssert:
|
|||
## [codeblock]
|
||||
## assert_vector(Vector2(1.2, 1.000001)).is_equal(Vector2(1.2, 1.000001))
|
||||
## [/codeblock]
|
||||
func assert_vector(current :Variant) -> GdUnitVectorAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd").new(current)
|
||||
func assert_vector(current :Variant, type_check := true) -> GdUnitVectorAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd").new(current, type_check)
|
||||
|
||||
|
||||
## An assertion tool to verify arrays.
|
||||
func assert_array(current :Variant) -> GdUnitArrayAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current)
|
||||
func assert_array(current :Variant, type_check := true) -> GdUnitArrayAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current, type_check)
|
||||
|
||||
|
||||
## An assertion tool to verify dictionaries.
|
||||
|
@ -577,6 +620,7 @@ func assert_signal(instance :Object) -> GdUnitSignalAssert:
|
|||
## .has_message("Expecting:\n 'true'\n not equal to\n 'true'")
|
||||
## [/codeblock]
|
||||
func assert_failure(assertion :Callable) -> GdUnitFailureAssert:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute(assertion)
|
||||
|
||||
|
||||
|
@ -588,6 +632,7 @@ func assert_failure(assertion :Callable) -> GdUnitFailureAssert:
|
|||
## .has_message("Expecting:\n 'true'\n not equal to\n 'true'")
|
||||
## [/codeblock]
|
||||
func assert_failure_await(assertion :Callable) -> GdUnitFailureAssert:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return await __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute_and_await(assertion)
|
||||
|
||||
|
||||
|
@ -608,10 +653,12 @@ func assert_error(current :Callable) -> GdUnitGodotErrorAssert:
|
|||
|
||||
|
||||
func assert_not_yet_implemented() -> void:
|
||||
__gdunit_assert().new(null).test_fail()
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__gdunit_assert().new(null).do_fail()
|
||||
|
||||
|
||||
func fail(message :String) -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__gdunit_assert().new(null).report_error(message)
|
||||
|
||||
|
||||
|
|
|
@ -8,8 +8,12 @@ const SUB_COLOR := Color(1, 0, 0, .3)
|
|||
const ADD_COLOR := Color(0, 1, 0, .3)
|
||||
|
||||
|
||||
static func format_dict(value :Dictionary) -> String:
|
||||
if value.is_empty():
|
||||
static func format_dict(value :Variant) -> String:
|
||||
if not value is Dictionary:
|
||||
return str(value)
|
||||
|
||||
var dict_value: Dictionary = value
|
||||
if dict_value.is_empty():
|
||||
return "{ }"
|
||||
var as_rows := var_to_str(value).split("\n")
|
||||
for index in range( 1, as_rows.size()-1):
|
||||
|
@ -22,15 +26,22 @@ static func format_dict(value :Dictionary) -> String:
|
|||
static func input_event_as_text(event :InputEvent) -> String:
|
||||
var text := ""
|
||||
if event is InputEventKey:
|
||||
var key_event := event as InputEventKey
|
||||
text += "InputEventKey : key='%s', pressed=%s, keycode=%d, physical_keycode=%s" % [
|
||||
event.as_text(), event.pressed, event.keycode, event.physical_keycode]
|
||||
event.as_text(), key_event.pressed, key_event.keycode, key_event.physical_keycode]
|
||||
else:
|
||||
text += event.as_text()
|
||||
if event is InputEventMouse:
|
||||
text += ", global_position %s" % event.global_position
|
||||
var mouse_event := event as InputEventMouse
|
||||
text += ", global_position %s" % mouse_event.global_position
|
||||
if event is InputEventWithModifiers:
|
||||
var mouse_event := event as InputEventWithModifiers
|
||||
text += ", shift=%s, alt=%s, control=%s, meta=%s, command=%s" % [
|
||||
event.shift_pressed, event.alt_pressed, event.ctrl_pressed, event.meta_pressed, event.command_or_control_autoremap]
|
||||
mouse_event.shift_pressed,
|
||||
mouse_event.alt_pressed,
|
||||
mouse_event.ctrl_pressed,
|
||||
mouse_event.meta_pressed,
|
||||
mouse_event.command_or_control_autoremap]
|
||||
return text
|
||||
|
||||
|
||||
|
@ -51,9 +62,11 @@ static func colored_array_div(characters :PackedByteArray) -> String:
|
|||
match character:
|
||||
GdDiffTool.DIV_ADD:
|
||||
index += 1
|
||||
@warning_ignore("return_value_discarded")
|
||||
additional_chars.append(characters[index])
|
||||
GdDiffTool.DIV_SUB:
|
||||
index += 1
|
||||
@warning_ignore("return_value_discarded")
|
||||
missing_chars.append(characters[index])
|
||||
_:
|
||||
if not missing_chars.is_empty():
|
||||
|
@ -62,6 +75,7 @@ static func colored_array_div(characters :PackedByteArray) -> String:
|
|||
if not additional_chars.is_empty():
|
||||
result.append_array(format_chars(additional_chars, ADD_COLOR))
|
||||
additional_chars = PackedByteArray()
|
||||
@warning_ignore("return_value_discarded")
|
||||
result.append(character)
|
||||
index += 1
|
||||
|
||||
|
@ -95,7 +109,7 @@ static func _nerror(number :Variant) -> String:
|
|||
static func _colored_value(value :Variant) -> String:
|
||||
match typeof(value):
|
||||
TYPE_STRING, TYPE_STRING_NAME:
|
||||
return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _colored_string_div(value)]
|
||||
return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _colored_string_div(str(value))]
|
||||
TYPE_INT:
|
||||
return "'[color=%s]%d[/color]'" % [VALUE_COLOR, value]
|
||||
TYPE_FLOAT:
|
||||
|
@ -106,10 +120,12 @@ static func _colored_value(value :Variant) -> String:
|
|||
if value == null:
|
||||
return "'[color=%s]<null>[/color]'" % [VALUE_COLOR]
|
||||
if value is InputEvent:
|
||||
return "[color=%s]<%s>[/color]" % [VALUE_COLOR, input_event_as_text(value)]
|
||||
if value.has_method("_to_string"):
|
||||
var ie: InputEvent = value
|
||||
return "[color=%s]<%s>[/color]" % [VALUE_COLOR, input_event_as_text(ie)]
|
||||
var obj_value: Object = value
|
||||
if obj_value.has_method("_to_string"):
|
||||
return "[color=%s]<%s>[/color]" % [VALUE_COLOR, str(value)]
|
||||
return "[color=%s]<%s>[/color]" % [VALUE_COLOR, value.get_class()]
|
||||
return "[color=%s]<%s>[/color]" % [VALUE_COLOR, obj_value.get_class()]
|
||||
TYPE_DICTIONARY:
|
||||
return "'[color=%s]%s[/color]'" % [VALUE_COLOR, format_dict(value)]
|
||||
_:
|
||||
|
@ -163,10 +179,10 @@ static func test_timeout(timeout :int) -> String:
|
|||
static func test_suite_skipped(hint :String, skip_count :int) -> String:
|
||||
return """
|
||||
%s
|
||||
Tests skipped: %s
|
||||
Skipped %s tests
|
||||
Reason: %s
|
||||
""".dedent().trim_prefix("\n")\
|
||||
% [_error("Entire test-suite is skipped!"), _colored_value(skip_count), _colored_value(hint)]
|
||||
% [_error("The Entire test-suite is skipped!"), _colored_value(skip_count), _colored_value(hint)]
|
||||
|
||||
|
||||
static func test_skipped(hint :String) -> String:
|
||||
|
@ -336,6 +352,7 @@ static func error_ends_with(current :Variant, expected :Variant) -> String:
|
|||
|
||||
|
||||
static func error_has_length(current :Variant, expected: int, compare_operator :int) -> String:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
var current_length :Variant = current.length() if current != null else null
|
||||
match compare_operator:
|
||||
Comparator.EQUAL:
|
||||
|
@ -361,48 +378,50 @@ static func error_has_length(current :Variant, expected: int, compare_operator :
|
|||
|
||||
# - ArrayAssert specific messgaes ---------------------------------------------------
|
||||
|
||||
static func error_arr_contains(current :Variant, expected :Array, not_expect :Array, not_found :Array, by_reference :bool) -> String:
|
||||
static func error_arr_contains(current: Variant, expected: Variant, not_expect: Variant, not_found: Variant, by_reference: bool) -> String:
|
||||
var failure_message := "Expecting contains SAME elements:" if by_reference else "Expecting contains elements:"
|
||||
var error := "%s\n %s\n do contains (in any order)\n %s" % [
|
||||
_error(failure_message), _colored_value(current), _colored_value(expected)]
|
||||
if not not_expect.is_empty():
|
||||
if not is_empty(not_expect):
|
||||
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
|
||||
if not not_found.is_empty():
|
||||
var prefix := "but" if not_expect.is_empty() else "and"
|
||||
if not is_empty(not_found):
|
||||
var prefix := "but" if is_empty(not_expect) else "and"
|
||||
error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)]
|
||||
return error
|
||||
|
||||
|
||||
static func error_arr_contains_exactly(
|
||||
current :Variant,
|
||||
expected :Variant,
|
||||
not_expect :Variant,
|
||||
not_found :Variant, compare_mode :GdObjects.COMPARE_MODE) -> String:
|
||||
current: Variant,
|
||||
expected: Variant,
|
||||
not_expect: Variant,
|
||||
not_found: Variant, compare_mode: GdObjects.COMPARE_MODE) -> String:
|
||||
var failure_message := (
|
||||
"Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
|
||||
else "Expecting contains SAME exactly elements:"
|
||||
)
|
||||
if not_expect.is_empty() and not_found.is_empty():
|
||||
var diff := _find_first_diff(current, expected)
|
||||
if is_empty(not_expect) and is_empty(not_found):
|
||||
var arr_current: Array = current
|
||||
var arr_expected: Array = expected
|
||||
var diff := _find_first_diff(arr_current, arr_expected)
|
||||
return "%s\n %s\n do contains (in same order)\n %s\n but has different order %s" % [
|
||||
_error(failure_message), _colored_value(current), _colored_value(expected), diff]
|
||||
|
||||
var error := "%s\n %s\n do contains (in same order)\n %s" % [
|
||||
_error(failure_message), _colored_value(current), _colored_value(expected)]
|
||||
if not not_expect.is_empty():
|
||||
if not is_empty(not_expect):
|
||||
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
|
||||
if not not_found.is_empty():
|
||||
var prefix := "but" if not_expect.is_empty() else "and"
|
||||
if not is_empty(not_found):
|
||||
var prefix := "but" if is_empty(not_expect) else "and"
|
||||
error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)]
|
||||
return error
|
||||
|
||||
|
||||
static func error_arr_contains_exactly_in_any_order(
|
||||
current :Variant,
|
||||
expected :Array,
|
||||
not_expect :Array,
|
||||
not_found :Array,
|
||||
compare_mode :GdObjects.COMPARE_MODE) -> String:
|
||||
current: Variant,
|
||||
expected: Variant,
|
||||
not_expect: Variant,
|
||||
not_found: Variant,
|
||||
compare_mode: GdObjects.COMPARE_MODE) -> String:
|
||||
|
||||
var failure_message := (
|
||||
"Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
|
||||
|
@ -410,19 +429,19 @@ static func error_arr_contains_exactly_in_any_order(
|
|||
)
|
||||
var error := "%s\n %s\n do contains exactly (in any order)\n %s" % [
|
||||
_error(failure_message), _colored_value(current), _colored_value(expected)]
|
||||
if not not_expect.is_empty():
|
||||
if not is_empty(not_expect):
|
||||
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
|
||||
if not not_found.is_empty():
|
||||
var prefix := "but" if not_expect.is_empty() else "and"
|
||||
if not is_empty(not_found):
|
||||
var prefix := "but" if is_empty(not_expect) else "and"
|
||||
error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)]
|
||||
return error
|
||||
|
||||
|
||||
static func error_arr_not_contains(current :Array, expected :Array, found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String:
|
||||
static func error_arr_not_contains(current: Variant, expected: Variant, found: Variant, compare_mode: GdObjects.COMPARE_MODE) -> String:
|
||||
var failure_message := "Expecting:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting SAME:"
|
||||
var error := "%s\n %s\n do not contains\n %s" % [
|
||||
_error(failure_message), _colored_value(current), _colored_value(expected)]
|
||||
if not found.is_empty():
|
||||
if not is_empty(found):
|
||||
error += "\n but found elements:\n %s" % _colored_value(found)
|
||||
return error
|
||||
|
||||
|
@ -542,20 +561,25 @@ static func result_message(result :GdUnitResult) -> String:
|
|||
# - Spy|Mock specific errors ----------------------------------------------------
|
||||
static func error_no_more_interactions(summary :Dictionary) -> String:
|
||||
var interactions := PackedStringArray()
|
||||
for args :Variant in summary.keys():
|
||||
for args :Array in summary.keys():
|
||||
var times :int = summary[args]
|
||||
@warning_ignore("return_value_discarded")
|
||||
interactions.append(_format_arguments(args, times))
|
||||
return "%s\n%s\n%s" % [_error("Expecting no more interactions!"), _error("But found interactions on:"), "\n".join(interactions)]
|
||||
|
||||
|
||||
static func error_validate_interactions(current_interactions :Dictionary, expected_interactions :Dictionary) -> String:
|
||||
var interactions := PackedStringArray()
|
||||
for args :Variant in current_interactions.keys():
|
||||
var times :int = current_interactions[args]
|
||||
interactions.append(_format_arguments(args, times))
|
||||
var expected_interaction := _format_arguments(expected_interactions.keys()[0], expected_interactions.values()[0])
|
||||
static func error_validate_interactions(current_interactions: Dictionary, expected_interactions: Dictionary) -> String:
|
||||
var collected_interactions := PackedStringArray()
|
||||
for args: Array in current_interactions.keys():
|
||||
var times: int = current_interactions[args]
|
||||
@warning_ignore("return_value_discarded")
|
||||
collected_interactions.append(_format_arguments(args, times))
|
||||
|
||||
var arguments: Array = expected_interactions.keys()[0]
|
||||
var interactions: int = expected_interactions.values()[0]
|
||||
var expected_interaction := _format_arguments(arguments, interactions)
|
||||
return "%s\n%s\n%s\n%s" % [
|
||||
_error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(interactions)]
|
||||
_error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(collected_interactions)]
|
||||
|
||||
|
||||
static func _format_arguments(args :Array, times :int) -> String:
|
||||
|
@ -569,13 +593,15 @@ static func _format_arguments(args :Array, times :int) -> String:
|
|||
static func _to_typed_args(args :Array) -> PackedStringArray:
|
||||
var typed := PackedStringArray()
|
||||
for arg :Variant in args:
|
||||
@warning_ignore("return_value_discarded")
|
||||
typed.append(_format_arg(arg) + " :" + GdObjects.type_as_string(typeof(arg)))
|
||||
return typed
|
||||
|
||||
|
||||
static func _format_arg(arg :Variant) -> String:
|
||||
if arg is InputEvent:
|
||||
return input_event_as_text(arg)
|
||||
var ie: InputEvent = arg
|
||||
return input_event_as_text(ie)
|
||||
return str(arg)
|
||||
|
||||
|
||||
|
@ -589,6 +615,7 @@ static func _find_first_diff(left :Array, right :Array) -> String:
|
|||
|
||||
|
||||
static func error_has_size(current :Variant, expected: int) -> String:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
var current_size :Variant = null if current == null else current.size()
|
||||
return "%s\n %s\n but was\n %s" % [_error("Expecting size:"), _colored_value(expected), _colored_value(current_size)]
|
||||
|
||||
|
@ -613,3 +640,18 @@ static func format_invalid(value :String) -> String:
|
|||
|
||||
static func humanized(value :String) -> String:
|
||||
return value.replace("_", " ")
|
||||
|
||||
|
||||
static func build_failure_message(failure :String, additional_failure_message: String, custom_failure_message: String) -> String:
|
||||
var message := failure if custom_failure_message.is_empty() else custom_failure_message
|
||||
if additional_failure_message.is_empty():
|
||||
return message
|
||||
return """
|
||||
%s
|
||||
[color=LIME_GREEN][b]Additional info:[/b][/color]
|
||||
%s""".dedent().trim_prefix("\n") % [message, additional_failure_message]
|
||||
|
||||
|
||||
static func is_empty(value: Variant) -> bool:
|
||||
var arry_value: Array = value
|
||||
return arry_value != null and arry_value.is_empty()
|
||||
|
|
|
@ -51,5 +51,4 @@ static func current_failure() -> String:
|
|||
|
||||
|
||||
static func send_report(report :GdUnitReport) -> void:
|
||||
var execution_context_id := GdUnitThreadManager.get_current_context().get_execution_context_id()
|
||||
GdUnitSignals.instance().gdunit_report.emit(execution_context_id, report)
|
||||
GdUnitThreadManager.get_current_context().get_execution_context().add_report(report)
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
class_name GdUnitArrayAssertImpl
|
||||
extends GdUnitArrayAssert
|
||||
|
||||
|
||||
var _base :GdUnitAssert
|
||||
var _current_value_provider :ValueProvider
|
||||
var _base: GdUnitAssertImpl
|
||||
var _current_value_provider: ValueProvider
|
||||
var _type_check: bool
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
func _init(current: Variant, type_check := true) -> void:
|
||||
_type_check = type_check
|
||||
_current_value_provider = DefaultValueProvider.new(current)
|
||||
_base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(current)
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if not _validate_value_type(current):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitArrayAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
|
||||
|
||||
|
||||
func _notification(event :int) -> void:
|
||||
func _notification(event: int) -> void:
|
||||
if event == NOTIFICATION_PREDELETE:
|
||||
if _base != null:
|
||||
_base.notification(event)
|
||||
|
@ -27,21 +30,26 @@ func report_success() -> GdUnitArrayAssert:
|
|||
return self
|
||||
|
||||
|
||||
func report_error(error :String) -> GdUnitArrayAssert:
|
||||
func report_error(error: String) -> GdUnitArrayAssert:
|
||||
_base.report_error(error)
|
||||
return self
|
||||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _base._current_error_message
|
||||
return _base.failure_message()
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitArrayAssert:
|
||||
func override_failure_message(message: String) -> GdUnitArrayAssert:
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func _validate_value_type(value :Variant) -> bool:
|
||||
func append_failure_message(message: String) -> GdUnitArrayAssert:
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func _validate_value_type(value: Variant) -> bool:
|
||||
return value == null or GdArrayTools.is_array_type(value)
|
||||
|
||||
|
||||
|
@ -49,15 +57,23 @@ func get_current_value() -> Variant:
|
|||
return _current_value_provider.get_value()
|
||||
|
||||
|
||||
func max_length(left :Variant, right :Variant) -> int:
|
||||
func max_length(left: Variant, right: Variant) -> int:
|
||||
var ls := str(left).length()
|
||||
var rs := str(right).length()
|
||||
return rs if ls < rs else ls
|
||||
|
||||
|
||||
func _array_equals_div(current :Array, expected :Array, case_sensitive :bool = false) -> Array:
|
||||
var current_value := PackedStringArray(current)
|
||||
var expected_value := PackedStringArray(expected)
|
||||
# gdlint: disable=function-name
|
||||
func _toPackedStringArray(value: Variant) -> PackedStringArray:
|
||||
if GdArrayTools.is_array_type(value):
|
||||
@warning_ignore("unsafe_cast")
|
||||
return PackedStringArray(value as Array)
|
||||
return PackedStringArray([str(value)])
|
||||
|
||||
|
||||
func _array_equals_div(current: Variant, expected: Variant, case_sensitive: bool = false) -> Array[Array]:
|
||||
var current_value := _toPackedStringArray(current)
|
||||
var expected_value := _toPackedStringArray(expected)
|
||||
var index_report := Array()
|
||||
for index in current_value.size():
|
||||
var c := current_value[index]
|
||||
|
@ -67,25 +83,25 @@ func _array_equals_div(current :Array, expected :Array, case_sensitive :bool = f
|
|||
var length := max_length(c, e)
|
||||
current_value[index] = GdAssertMessages.format_invalid(c.lpad(length))
|
||||
expected_value[index] = e.lpad(length)
|
||||
index_report.push_back({"index" : index, "current" :c, "expected": e})
|
||||
index_report.push_back({"index": index, "current": c, "expected": e})
|
||||
else:
|
||||
current_value[index] = GdAssertMessages.format_invalid(c)
|
||||
index_report.push_back({"index" : index, "current" :c, "expected": "<N/A>"})
|
||||
index_report.push_back({"index": index, "current": c, "expected": "<N/A>"})
|
||||
|
||||
for index in range(current.size(), expected_value.size()):
|
||||
for index in range(current_value.size(), expected_value.size()):
|
||||
var value := expected_value[index]
|
||||
expected_value[index] = GdAssertMessages.format_invalid(value)
|
||||
index_report.push_back({"index" : index, "current" : "<N/A>", "expected": value})
|
||||
index_report.push_back({"index": index, "current": "<N/A>", "expected": value})
|
||||
return [current_value, expected_value, index_report]
|
||||
|
||||
|
||||
func _array_div(compare_mode :GdObjects.COMPARE_MODE, left :Array[Variant], right :Array[Variant], _same_order := false) -> Array[Variant]:
|
||||
func _array_div(compare_mode: GdObjects.COMPARE_MODE, left: Array[Variant], right: Array[Variant], _same_order := false) -> Array[Variant]:
|
||||
var not_expect := left.duplicate(true)
|
||||
var not_found := right.duplicate(true)
|
||||
for index_c in left.size():
|
||||
var c :Variant = left[index_c]
|
||||
var c: Variant = left[index_c]
|
||||
for index_e in right.size():
|
||||
var e :Variant = right[index_e]
|
||||
var e: Variant = right[index_e]
|
||||
if GdObjects.equals(c, e, false, compare_mode):
|
||||
GdArrayTools.erase_value(not_expect, e)
|
||||
GdArrayTools.erase_value(not_found, c)
|
||||
|
@ -93,241 +109,262 @@ func _array_div(compare_mode :GdObjects.COMPARE_MODE, left :Array[Variant], righ
|
|||
return [not_expect, not_found]
|
||||
|
||||
|
||||
func _contains(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
func _contains(expected: Variant, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var by_reference := compare_mode == GdObjects.COMPARE_MODE.OBJECT_REFERENCE
|
||||
var current_value :Variant = get_current_value()
|
||||
var current_value: Variant = get_current_value()
|
||||
if current_value == null:
|
||||
return report_error(GdAssertMessages.error_arr_contains(current_value, expected, [], expected, by_reference))
|
||||
var diffs := _array_div(compare_mode, current_value, expected)
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diffs := _array_div(compare_mode, current_value as Array[Variant], expected as Array[Variant])
|
||||
#var not_expect := diffs[0] as Array
|
||||
var not_found := diffs[1] as Array
|
||||
var not_found: Array = diffs[1]
|
||||
if not not_found.is_empty():
|
||||
return report_error(GdAssertMessages.error_arr_contains(current_value, expected, [], not_found, by_reference))
|
||||
return report_success()
|
||||
|
||||
|
||||
func _contains_exactly(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
func _contains_exactly(expected: Variant, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current_value :Variant = get_current_value()
|
||||
var current_value: Variant = get_current_value()
|
||||
if current_value == null:
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, [], expected, compare_mode))
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly(null, expected, [], expected, compare_mode))
|
||||
# has same content in same order
|
||||
if GdObjects.equals(Array(current_value), Array(expected), false, compare_mode):
|
||||
if _is_equal(current_value, expected, false, compare_mode):
|
||||
return report_success()
|
||||
# check has same elements but in different order
|
||||
if GdObjects.equals_sorted(Array(current_value), Array(expected), false, compare_mode):
|
||||
if _is_equals_sorted(current_value, expected, false, compare_mode):
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, [], [], compare_mode))
|
||||
# find the difference
|
||||
var diffs := _array_div(compare_mode, current_value, expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
var not_expect := diffs[0] as Array[Variant]
|
||||
var not_found := diffs[1] as Array[Variant]
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diffs := _array_div(compare_mode,
|
||||
current_value as Array[Variant],
|
||||
expected as Array[Variant],
|
||||
GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
var not_expect: Array[Variant] = diffs[0]
|
||||
var not_found: Array[Variant] = diffs[1]
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, not_expect, not_found, compare_mode))
|
||||
|
||||
|
||||
func _contains_exactly_in_any_order(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
func _contains_exactly_in_any_order(expected: Variant, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current_value :Variant = get_current_value()
|
||||
var current_value: Variant = get_current_value()
|
||||
if current_value == null:
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, [], expected, compare_mode))
|
||||
# find the difference
|
||||
var diffs := _array_div(compare_mode, current_value, expected, false)
|
||||
var not_expect := diffs[0] as Array
|
||||
var not_found := diffs[1] as Array
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diffs := _array_div(compare_mode, current_value as Array[Variant], expected as Array[Variant], false)
|
||||
var not_expect: Array[Variant] = diffs[0]
|
||||
var not_found: Array[Variant] = diffs[1]
|
||||
if not_expect.is_empty() and not_found.is_empty():
|
||||
return report_success()
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, not_expect, not_found, compare_mode))
|
||||
|
||||
|
||||
func _not_contains(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
func _not_contains(expected: Variant, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current_value :Variant = get_current_value()
|
||||
var current_value: Variant = get_current_value()
|
||||
if current_value == null:
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, [], expected, compare_mode))
|
||||
var diffs := _array_div(compare_mode, current_value, expected)
|
||||
var found := diffs[0] as Array
|
||||
if found.size() == current_value.size():
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diffs := _array_div(compare_mode, current_value as Array[Variant], expected as Array[Variant])
|
||||
var found: Array[Variant] = diffs[0]
|
||||
@warning_ignore("unsafe_cast")
|
||||
if found.size() == (current_value as Array).size():
|
||||
return report_success()
|
||||
var diffs2 := _array_div(compare_mode, expected, diffs[1])
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diffs2 := _array_div(compare_mode, expected as Array[Variant], diffs[1] as Array[Variant])
|
||||
return report_error(GdAssertMessages.error_arr_not_contains(current_value, expected, diffs2[0], compare_mode))
|
||||
|
||||
|
||||
func is_null() -> GdUnitArrayAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_not_null() -> GdUnitArrayAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_null()
|
||||
return self
|
||||
|
||||
|
||||
# Verifies that the current String is equal to the given one.
|
||||
func is_equal(expected :Variant) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
func is_equal(expected: Variant) -> GdUnitArrayAssert:
|
||||
if _type_check and not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current_value :Variant = get_current_value()
|
||||
var current_value: Variant = get_current_value()
|
||||
if current_value == null and expected != null:
|
||||
return report_error(GdAssertMessages.error_equal(null, expected))
|
||||
if not GdObjects.equals(current_value, expected):
|
||||
if not _is_equal(current_value, expected):
|
||||
var diff := _array_equals_div(current_value, expected)
|
||||
var expected_as_list := GdArrayTools.as_string(diff[0], false)
|
||||
var current_as_list := GdArrayTools.as_string(diff[1], false)
|
||||
var index_report :Variant = diff[2]
|
||||
var index_report: Array = diff[2]
|
||||
return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report))
|
||||
return report_success()
|
||||
|
||||
|
||||
# Verifies that the current Array is equal to the given one, ignoring case considerations.
|
||||
func is_equal_ignoring_case(expected :Variant) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
func is_equal_ignoring_case(expected: Variant) -> GdUnitArrayAssert:
|
||||
if _type_check and not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current_value :Variant = get_current_value()
|
||||
var current_value: Variant = get_current_value()
|
||||
if current_value == null and expected != null:
|
||||
return report_error(GdAssertMessages.error_equal(null, GdArrayTools.as_string(expected)))
|
||||
if not GdObjects.equals(current_value, expected, true):
|
||||
var diff := _array_equals_div(current_value, expected, true)
|
||||
@warning_ignore("unsafe_cast")
|
||||
return report_error(GdAssertMessages.error_equal(null, GdArrayTools.as_string(expected as Array)))
|
||||
if not _is_equal(current_value, expected, true):
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diff := _array_equals_div(current_value as Array[Variant], expected as Array[Variant], true)
|
||||
var expected_as_list := GdArrayTools.as_string(diff[0])
|
||||
var current_as_list := GdArrayTools.as_string(diff[1])
|
||||
var index_report :Variant = diff[2]
|
||||
var index_report: Array = diff[2]
|
||||
return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_equal(expected :Variant) -> GdUnitArrayAssert:
|
||||
func is_not_equal(expected: Variant) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current_value :Variant = get_current_value()
|
||||
if GdObjects.equals(current_value, expected):
|
||||
var current_value: Variant = get_current_value()
|
||||
if _is_equal(current_value, expected):
|
||||
return report_error(GdAssertMessages.error_not_equal(current_value, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_equal_ignoring_case(expected :Variant) -> GdUnitArrayAssert:
|
||||
func is_not_equal_ignoring_case(expected: Variant) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current_value :Variant = get_current_value()
|
||||
if GdObjects.equals(current_value, expected, true):
|
||||
var c := GdArrayTools.as_string(current_value)
|
||||
var e := GdArrayTools.as_string(expected)
|
||||
var current_value: Variant = get_current_value()
|
||||
if _is_equal(current_value, expected, true):
|
||||
@warning_ignore("unsafe_cast")
|
||||
var c := GdArrayTools.as_string(current_value as Array)
|
||||
@warning_ignore("unsafe_cast")
|
||||
var e := GdArrayTools.as_string(expected as Array)
|
||||
return report_error(GdAssertMessages.error_not_equal_case_insensetiv(c, e))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_empty() -> GdUnitArrayAssert:
|
||||
var current_value :Variant = get_current_value()
|
||||
if current_value == null or current_value.size() > 0:
|
||||
var current_value: Variant = get_current_value()
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current_value == null or (current_value as Array).size() > 0:
|
||||
return report_error(GdAssertMessages.error_is_empty(current_value))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_empty() -> GdUnitArrayAssert:
|
||||
var current_value :Variant = get_current_value()
|
||||
if current_value != null and current_value.size() == 0:
|
||||
var current_value: Variant = get_current_value()
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current_value != null and (current_value as Array).size() == 0:
|
||||
return report_error(GdAssertMessages.error_is_not_empty())
|
||||
return report_success()
|
||||
|
||||
|
||||
@warning_ignore("unused_parameter", "shadowed_global_identifier")
|
||||
func is_same(expected :Variant) -> GdUnitArrayAssert:
|
||||
func is_same(expected: Variant) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current :Variant = get_current_value()
|
||||
var current: Variant = get_current_value()
|
||||
if not is_same(current, expected):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error(GdAssertMessages.error_is_same(current, expected))
|
||||
return self
|
||||
|
||||
|
||||
func is_not_same(expected :Variant) -> GdUnitArrayAssert:
|
||||
func is_not_same(expected: Variant) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current :Variant = get_current_value()
|
||||
var current: Variant = get_current_value()
|
||||
if is_same(current, expected):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error(GdAssertMessages.error_not_same(current, expected))
|
||||
return self
|
||||
|
||||
|
||||
func has_size(expected: int) -> GdUnitArrayAssert:
|
||||
var current_value :Variant= get_current_value()
|
||||
if current_value == null or current_value.size() != expected:
|
||||
var current_value: Variant = get_current_value()
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current_value == null or (current_value as Array).size() != expected:
|
||||
return report_error(GdAssertMessages.error_has_size(current_value, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func contains(expected :Variant) -> GdUnitArrayAssert:
|
||||
func contains(expected: Variant) -> GdUnitArrayAssert:
|
||||
return _contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
|
||||
|
||||
func contains_exactly(expected :Variant) -> GdUnitArrayAssert:
|
||||
func contains_exactly(expected: Variant) -> GdUnitArrayAssert:
|
||||
return _contains_exactly(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
|
||||
|
||||
func contains_exactly_in_any_order(expected :Variant) -> GdUnitArrayAssert:
|
||||
func contains_exactly_in_any_order(expected: Variant) -> GdUnitArrayAssert:
|
||||
return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
|
||||
|
||||
func contains_same(expected :Variant) -> GdUnitArrayAssert:
|
||||
func contains_same(expected: Variant) -> GdUnitArrayAssert:
|
||||
return _contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
|
||||
|
||||
|
||||
func contains_same_exactly(expected :Variant) -> GdUnitArrayAssert:
|
||||
func contains_same_exactly(expected: Variant) -> GdUnitArrayAssert:
|
||||
return _contains_exactly(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
|
||||
|
||||
|
||||
func contains_same_exactly_in_any_order(expected :Variant) -> GdUnitArrayAssert:
|
||||
func contains_same_exactly_in_any_order(expected: Variant) -> GdUnitArrayAssert:
|
||||
return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
|
||||
|
||||
|
||||
func not_contains(expected :Variant) -> GdUnitArrayAssert:
|
||||
func not_contains(expected: Variant) -> GdUnitArrayAssert:
|
||||
return _not_contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
|
||||
|
||||
func not_contains_same(expected :Variant) -> GdUnitArrayAssert:
|
||||
func not_contains_same(expected: Variant) -> GdUnitArrayAssert:
|
||||
return _not_contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
|
||||
|
||||
|
||||
func is_instanceof(expected :Variant) -> GdUnitAssert:
|
||||
func is_instanceof(expected: Variant) -> GdUnitAssert:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
_base.is_instanceof(expected)
|
||||
return self
|
||||
|
||||
|
||||
func extract(func_name :String, args := Array()) -> GdUnitArrayAssert:
|
||||
func extract(func_name: String, args := Array()) -> GdUnitArrayAssert:
|
||||
var extracted_elements := Array()
|
||||
var extractor :GdUnitValueExtractor = ResourceLoader.load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd",
|
||||
"GDScript", ResourceLoader.CACHE_MODE_REUSE).new(func_name, args)
|
||||
var current :Variant = get_current_value()
|
||||
|
||||
var extractor := GdUnitFuncValueExtractor.new(func_name, args)
|
||||
var current: Variant = get_current_value()
|
||||
if current == null:
|
||||
_current_value_provider = DefaultValueProvider.new(null)
|
||||
else:
|
||||
for element :Variant in current:
|
||||
for element: Variant in current:
|
||||
extracted_elements.append(extractor.extract_value(element))
|
||||
_current_value_provider = DefaultValueProvider.new(extracted_elements)
|
||||
return self
|
||||
|
||||
|
||||
func extractv(
|
||||
extr0 :GdUnitValueExtractor,
|
||||
extr1 :GdUnitValueExtractor = null,
|
||||
extr2 :GdUnitValueExtractor = null,
|
||||
extr3 :GdUnitValueExtractor = null,
|
||||
extr4 :GdUnitValueExtractor = null,
|
||||
extr5 :GdUnitValueExtractor = null,
|
||||
extr6 :GdUnitValueExtractor = null,
|
||||
extr7 :GdUnitValueExtractor = null,
|
||||
extr8 :GdUnitValueExtractor = null,
|
||||
extr9 :GdUnitValueExtractor = null) -> GdUnitArrayAssert:
|
||||
var extractors :Variant = GdArrayTools.filter_value([extr0, extr1, extr2, extr3, extr4, extr5, extr6, extr7, extr8, extr9], null)
|
||||
extr0: GdUnitValueExtractor,
|
||||
extr1: GdUnitValueExtractor = null,
|
||||
extr2: GdUnitValueExtractor = null,
|
||||
extr3: GdUnitValueExtractor = null,
|
||||
extr4: GdUnitValueExtractor = null,
|
||||
extr5: GdUnitValueExtractor = null,
|
||||
extr6: GdUnitValueExtractor = null,
|
||||
extr7: GdUnitValueExtractor = null,
|
||||
extr8: GdUnitValueExtractor = null,
|
||||
extr9: GdUnitValueExtractor = null) -> GdUnitArrayAssert:
|
||||
var extractors: Variant = GdArrayTools.filter_value([extr0, extr1, extr2, extr3, extr4, extr5, extr6, extr7, extr8, extr9], null)
|
||||
var extracted_elements := Array()
|
||||
var current :Variant = get_current_value()
|
||||
var current: Variant = get_current_value()
|
||||
if current == null:
|
||||
_current_value_provider = DefaultValueProvider.new(null)
|
||||
else:
|
||||
for element: Variant in current:
|
||||
var ev :Array[Variant] = [
|
||||
var ev: Array[Variant] = [
|
||||
GdUnitTuple.NO_ARG,
|
||||
GdUnitTuple.NO_ARG,
|
||||
GdUnitTuple.NO_ARG,
|
||||
|
@ -339,12 +376,44 @@ func extractv(
|
|||
GdUnitTuple.NO_ARG,
|
||||
GdUnitTuple.NO_ARG
|
||||
]
|
||||
for index :int in extractors.size():
|
||||
var extractor :GdUnitValueExtractor = extractors[index]
|
||||
@warning_ignore("unsafe_cast")
|
||||
for index: int in (extractors as Array).size():
|
||||
var extractor: GdUnitValueExtractor = extractors[index]
|
||||
ev[index] = extractor.extract_value(element)
|
||||
if extractors.size() > 1:
|
||||
@warning_ignore("unsafe_cast")
|
||||
if (extractors as Array).size() > 1:
|
||||
extracted_elements.append(GdUnitTuple.new(ev[0], ev[1], ev[2], ev[3], ev[4], ev[5], ev[6], ev[7], ev[8], ev[9]))
|
||||
else:
|
||||
extracted_elements.append(ev[0])
|
||||
_current_value_provider = DefaultValueProvider.new(extracted_elements)
|
||||
return self
|
||||
|
||||
|
||||
@warning_ignore("incompatible_ternary")
|
||||
func _is_equal(
|
||||
left: Variant,
|
||||
right: Variant,
|
||||
case_sensitive := false,
|
||||
compare_mode := GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
|
||||
|
||||
@warning_ignore("unsafe_cast")
|
||||
return GdObjects.equals(
|
||||
(left as Array) if GdArrayTools.is_array_type(left) else left,
|
||||
(right as Array) if GdArrayTools.is_array_type(right) else right,
|
||||
case_sensitive,
|
||||
compare_mode
|
||||
)
|
||||
|
||||
|
||||
func _is_equals_sorted(
|
||||
left: Variant,
|
||||
right: Variant,
|
||||
case_sensitive := false,
|
||||
compare_mode := GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
|
||||
|
||||
@warning_ignore("unsafe_cast")
|
||||
return GdObjects.equals_sorted(
|
||||
left as Array,
|
||||
right as Array,
|
||||
case_sensitive,
|
||||
compare_mode)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
class_name GdUnitAssertImpl
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
var _current :Variant
|
||||
var _current_error_message :String = ""
|
||||
var _current_failure_message :String = ""
|
||||
var _custom_failure_message :String = ""
|
||||
var _additional_failure_message: String = ""
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
|
@ -13,8 +15,9 @@ func _init(current :Variant) -> void:
|
|||
GdAssertReports.reset_last_error_line_number()
|
||||
|
||||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _current_error_message
|
||||
return _current_failure_message
|
||||
|
||||
|
||||
func current_value() -> Variant:
|
||||
|
@ -26,16 +29,16 @@ func report_success() -> GdUnitAssert:
|
|||
return self
|
||||
|
||||
|
||||
func report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert:
|
||||
func report_error(failure :String, failure_line_number: int = -1) -> GdUnitAssert:
|
||||
var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number()
|
||||
GdAssertReports.set_last_error_line_number(line_number)
|
||||
_current_error_message = error_message if _custom_failure_message.is_empty() else _custom_failure_message
|
||||
GdAssertReports.report_error(_current_error_message, line_number)
|
||||
_current_failure_message = GdAssertMessages.build_failure_message(failure, _additional_failure_message, _custom_failure_message)
|
||||
GdAssertReports.report_error(_current_failure_message, line_number)
|
||||
Engine.set_meta("GD_TEST_FAILURE", true)
|
||||
return self
|
||||
|
||||
|
||||
func test_fail() -> GdUnitAssert:
|
||||
func do_fail() -> GdUnitAssert:
|
||||
return report_error(GdAssertMessages.error_not_implemented())
|
||||
|
||||
|
||||
|
@ -44,6 +47,11 @@ func override_failure_message(message :String) -> GdUnitAssert:
|
|||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitAssert:
|
||||
_additional_failure_message = message
|
||||
return self
|
||||
|
||||
|
||||
func is_equal(expected :Variant) -> GdUnitAssert:
|
||||
var current :Variant = current_value()
|
||||
if not GdObjects.equals(current, expected):
|
||||
|
|
|
@ -3,6 +3,7 @@ class_name GdUnitAssertions
|
|||
extends RefCounted
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func _init() -> void:
|
||||
# preload all gdunit assertions to speedup testsuite loading time
|
||||
# gdlint:disable=private-method-call
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
extends GdUnitBoolAssert
|
||||
|
||||
var _base: GdUnitAssert
|
||||
var _base: GdUnitAssertImpl
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
_base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(current)
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if not GdUnitAssertions.validate_value_type(current, TYPE_BOOL):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitBoolAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
|
||||
|
||||
|
||||
|
@ -34,32 +34,43 @@ func report_error(error :String) -> GdUnitBoolAssert:
|
|||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _base._current_error_message
|
||||
return _base.failure_message()
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitBoolAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitBoolAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
# Verifies that the current value is null.
|
||||
func is_null() -> GdUnitBoolAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_null()
|
||||
return self
|
||||
|
||||
|
||||
# Verifies that the current value is not null.
|
||||
func is_not_null() -> GdUnitBoolAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_equal(expected :Variant) -> GdUnitBoolAssert:
|
||||
func is_equal(expected: Variant) -> GdUnitBoolAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_equal(expected)
|
||||
return self
|
||||
|
||||
|
||||
func is_not_equal(expected :Variant) -> GdUnitBoolAssert:
|
||||
func is_not_equal(expected: Variant) -> GdUnitBoolAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_equal(expected)
|
||||
return self
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
extends GdUnitDictionaryAssert
|
||||
|
||||
var _base :GdUnitAssert
|
||||
var _base: GdUnitAssertImpl
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
_base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(current)
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if not GdUnitAssertions.validate_value_type(current, TYPE_DICTIONARY):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitDictionaryAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
|
||||
|
||||
|
||||
|
@ -30,24 +30,33 @@ func report_error(error :String) -> GdUnitDictionaryAssert:
|
|||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _base._current_error_message
|
||||
return _base.failure_message()
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitDictionaryAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitDictionaryAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func current_value() -> Variant:
|
||||
return _base.current_value()
|
||||
|
||||
|
||||
func is_null() -> GdUnitDictionaryAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_not_null() -> GdUnitDictionaryAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_null()
|
||||
return self
|
||||
|
||||
|
@ -96,14 +105,16 @@ func is_not_same(expected :Variant) -> GdUnitDictionaryAssert:
|
|||
|
||||
func is_empty() -> GdUnitDictionaryAssert:
|
||||
var current :Variant = current_value()
|
||||
if current == null or not current.is_empty():
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current == null or not (current as Dictionary).is_empty():
|
||||
return report_error(GdAssertMessages.error_is_empty(current))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_empty() -> GdUnitDictionaryAssert:
|
||||
var current :Variant = current_value()
|
||||
if current == null or current.is_empty():
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current == null or (current as Dictionary).is_empty():
|
||||
return report_error(GdAssertMessages.error_is_not_empty())
|
||||
return report_success()
|
||||
|
||||
|
@ -112,7 +123,8 @@ func has_size(expected: int) -> GdUnitDictionaryAssert:
|
|||
var current :Variant = current_value()
|
||||
if current == null:
|
||||
return report_error(GdAssertMessages.error_is_not_null())
|
||||
if current.size() != expected:
|
||||
@warning_ignore("unsafe_cast")
|
||||
if (current as Dictionary).size() != expected:
|
||||
return report_error(GdAssertMessages.error_has_size(current, expected))
|
||||
return report_success()
|
||||
|
||||
|
@ -122,9 +134,11 @@ func _contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -> Gd
|
|||
if current == null:
|
||||
return report_error(GdAssertMessages.error_is_not_null())
|
||||
# find expected keys
|
||||
var keys_not_found :Array = expected.filter(_filter_by_key.bind(current.keys(), compare_mode))
|
||||
@warning_ignore("unsafe_cast")
|
||||
var keys_not_found :Array = expected.filter(_filter_by_key.bind((current as Dictionary).keys(), compare_mode))
|
||||
if not keys_not_found.is_empty():
|
||||
return report_error(GdAssertMessages.error_contains_keys(current.keys(), expected, keys_not_found, compare_mode))
|
||||
@warning_ignore("unsafe_cast")
|
||||
return report_error(GdAssertMessages.error_contains_keys((current as Dictionary).keys() as Array, expected, keys_not_found, compare_mode))
|
||||
return report_success()
|
||||
|
||||
|
||||
|
@ -133,11 +147,12 @@ func _contains_key_value(key :Variant, value :Variant, compare_mode :GdObjects.C
|
|||
var expected := [key]
|
||||
if current == null:
|
||||
return report_error(GdAssertMessages.error_is_not_null())
|
||||
var keys_not_found :Array = expected.filter(_filter_by_key.bind(current.keys(), compare_mode))
|
||||
var dict_current: Dictionary = current
|
||||
var keys_not_found :Array = expected.filter(_filter_by_key.bind(dict_current.keys(), compare_mode))
|
||||
if not keys_not_found.is_empty():
|
||||
return report_error(GdAssertMessages.error_contains_keys(current.keys(), expected, keys_not_found, compare_mode))
|
||||
if not GdObjects.equals(current[key], value, false, compare_mode):
|
||||
return report_error(GdAssertMessages.error_contains_key_value(key, value, current[key], compare_mode))
|
||||
return report_error(GdAssertMessages.error_contains_keys(dict_current.keys() as Array, expected, keys_not_found, compare_mode))
|
||||
if not GdObjects.equals(dict_current[key], value, false, compare_mode):
|
||||
return report_error(GdAssertMessages.error_contains_key_value(key, value, dict_current[key], compare_mode))
|
||||
return report_success()
|
||||
|
||||
|
||||
|
@ -145,9 +160,10 @@ func _not_contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -
|
|||
var current :Variant = current_value()
|
||||
if current == null:
|
||||
return report_error(GdAssertMessages.error_is_not_null())
|
||||
var keys_found :Array = current.keys().filter(_filter_by_key.bind(expected, compare_mode, true))
|
||||
var dict_current: Dictionary = current
|
||||
var keys_found :Array = dict_current.keys().filter(_filter_by_key.bind(expected, compare_mode, true))
|
||||
if not keys_found.is_empty():
|
||||
return report_error(GdAssertMessages.error_not_contains_keys(current.keys(), expected, keys_found, compare_mode))
|
||||
return report_error(GdAssertMessages.error_not_contains_keys(dict_current.keys() as Array, expected, keys_found, compare_mode))
|
||||
return report_success()
|
||||
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ func execute_and_await(assertion :Callable, do_await := true) -> GdUnitFailureAs
|
|||
_set_do_expect_fail(true)
|
||||
var thread_context := GdUnitThreadManager.get_current_context()
|
||||
thread_context.set_assert(null)
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitSignals.instance().gdunit_set_test_failed.connect(_on_test_failed)
|
||||
# execute the given assertion as callable
|
||||
if do_await:
|
||||
|
@ -28,11 +29,13 @@ func execute_and_await(assertion :Callable, do_await := true) -> GdUnitFailureAs
|
|||
_is_failed = true
|
||||
_failure_message = "Invalid Callable! It must be a callable of 'GdUnitAssert'"
|
||||
return self
|
||||
@warning_ignore("unsafe_method_access")
|
||||
_failure_message = current_assert.failure_message()
|
||||
return self
|
||||
|
||||
|
||||
func execute(assertion :Callable) -> GdUnitFailureAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
execute_and_await(assertion, false)
|
||||
return self
|
||||
|
||||
|
@ -42,12 +45,12 @@ func _on_test_failed(value :bool) -> void:
|
|||
|
||||
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert:
|
||||
func is_equal(_expected: Variant) -> GdUnitFailureAssert:
|
||||
return _report_error("Not implemented")
|
||||
|
||||
|
||||
@warning_ignore("unused_parameter")
|
||||
func is_not_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert:
|
||||
func is_not_equal(_expected: Variant) -> GdUnitFailureAssert:
|
||||
return _report_error("Not implemented")
|
||||
|
||||
|
||||
|
@ -79,13 +82,24 @@ func has_line(expected :int) -> GdUnitFailureAssert:
|
|||
|
||||
|
||||
func has_message(expected :String) -> GdUnitFailureAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
is_failed()
|
||||
var expected_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(expected))
|
||||
var current_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(_failure_message))
|
||||
if current_error != expected_error:
|
||||
var diffs := GdDiffTool.string_diff(current_error, expected_error)
|
||||
var current := GdAssertMessages.colored_array_div(diffs[1])
|
||||
_report_error(GdAssertMessages.error_not_same_error(current, expected_error))
|
||||
return _report_error(GdAssertMessages.error_not_same_error(current, expected_error))
|
||||
return self
|
||||
|
||||
|
||||
func contains_message(expected :String) -> GdUnitFailureAssert:
|
||||
var expected_error := GdUnitTools.normalize_text(expected)
|
||||
var current_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(_failure_message))
|
||||
if not current_error.contains(expected_error):
|
||||
var diffs := GdDiffTool.string_diff(current_error, expected_error)
|
||||
var current := GdAssertMessages.colored_array_div(diffs[1])
|
||||
return _report_error(GdAssertMessages.error_not_same_error(current, expected_error))
|
||||
return self
|
||||
|
||||
|
||||
|
@ -95,7 +109,7 @@ func starts_with_message(expected :String) -> GdUnitFailureAssert:
|
|||
if current_error.find(expected_error) != 0:
|
||||
var diffs := GdDiffTool.string_diff(current_error, expected_error)
|
||||
var current := GdAssertMessages.colored_array_div(diffs[1])
|
||||
_report_error(GdAssertMessages.error_not_same_error(current, expected_error))
|
||||
return _report_error(GdAssertMessages.error_not_same_error(current, expected_error))
|
||||
return self
|
||||
|
||||
|
||||
|
|
|
@ -2,15 +2,15 @@ extends GdUnitFileAssert
|
|||
|
||||
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
|
||||
var _base: GdUnitAssert
|
||||
var _base: GdUnitAssertImpl
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
_base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(current)
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if not GdUnitAssertions.validate_value_type(current, TYPE_STRING):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitFileAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
|
||||
|
||||
|
||||
|
@ -22,7 +22,7 @@ func _notification(event :int) -> void:
|
|||
|
||||
|
||||
func current_value() -> String:
|
||||
return _base.current_value() as String
|
||||
return _base.current_value()
|
||||
|
||||
|
||||
func report_success() -> GdUnitFileAssert:
|
||||
|
@ -36,20 +36,29 @@ func report_error(error :String) -> GdUnitFileAssert:
|
|||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _base._current_error_message
|
||||
return _base.failure_message()
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitFileAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitFileAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func is_equal(expected :Variant) -> GdUnitFileAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_equal(expected)
|
||||
return self
|
||||
|
||||
|
||||
func is_not_equal(expected :Variant) -> GdUnitFileAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_equal(expected)
|
||||
return self
|
||||
|
||||
|
@ -79,17 +88,14 @@ func is_script() -> GdUnitFileAssert:
|
|||
return report_success()
|
||||
|
||||
|
||||
func contains_exactly(expected_rows :Array) -> GdUnitFileAssert:
|
||||
func contains_exactly(expected_rows: Array) -> GdUnitFileAssert:
|
||||
var current := current_value()
|
||||
if FileAccess.open(current, FileAccess.READ) == null:
|
||||
return report_error("Can't acces the file '%s'! Error code %s" % [current, FileAccess.get_open_error()])
|
||||
|
||||
var script := load(current)
|
||||
var script: GDScript = load(current)
|
||||
if script is GDScript:
|
||||
var instance :Variant = script.new()
|
||||
var source_code := GdScriptParser.to_unix_format(instance.get_script().source_code)
|
||||
GdUnitTools.free_instance(instance)
|
||||
var source_code := GdScriptParser.to_unix_format(script.source_code)
|
||||
var rows := Array(source_code.split("\n"))
|
||||
ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(rows).contains_exactly(expected_rows)
|
||||
GdUnitArrayAssertImpl.new(rows).contains_exactly(expected_rows)
|
||||
return self
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
extends GdUnitFloatAssert
|
||||
|
||||
var _base: GdUnitAssert
|
||||
var _base: GdUnitAssertImpl
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
_base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(current)
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if not GdUnitAssertions.validate_value_type(current, TYPE_FLOAT):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitFloatAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
|
||||
|
||||
|
||||
|
@ -34,30 +34,41 @@ func report_error(error :String) -> GdUnitFloatAssert:
|
|||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _base._current_error_message
|
||||
return _base.failure_message()
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitFloatAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitFloatAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func is_null() -> GdUnitFloatAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_not_null() -> GdUnitFloatAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_equal(expected :float) -> GdUnitFloatAssert:
|
||||
func is_equal(expected :Variant) -> GdUnitFloatAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_equal(expected)
|
||||
return self
|
||||
|
||||
|
||||
func is_not_equal(expected :float) -> GdUnitFloatAssert:
|
||||
func is_not_equal(expected :Variant) -> GdUnitFloatAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_equal(expected)
|
||||
return self
|
||||
|
||||
|
@ -111,14 +122,16 @@ func is_not_negative() -> GdUnitFloatAssert:
|
|||
|
||||
func is_zero() -> GdUnitFloatAssert:
|
||||
var current :Variant = current_value()
|
||||
if current == null or not is_equal_approx(0.00000000, current):
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current == null or not is_equal_approx(0.00000000, current as float):
|
||||
return report_error(GdAssertMessages.error_is_zero(current))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_zero() -> GdUnitFloatAssert:
|
||||
var current :Variant = current_value()
|
||||
if current == null or is_equal_approx(0.00000000, current):
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current == null or is_equal_approx(0.00000000, current as float):
|
||||
return report_error(GdAssertMessages.error_is_not_zero())
|
||||
return report_success()
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@ const DEFAULT_TIMEOUT := 2000
|
|||
|
||||
|
||||
var _current_value_provider :ValueProvider
|
||||
var _current_error_message :String = ""
|
||||
var _current_failure_message :String = ""
|
||||
var _custom_failure_message :String = ""
|
||||
var _additional_failure_message: String = ""
|
||||
var _line_number := -1
|
||||
var _timeout := DEFAULT_TIMEOUT
|
||||
var _interrupted := false
|
||||
|
@ -21,21 +22,26 @@ func _init(instance :Object, func_name :String, args := Array()) -> void:
|
|||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
# verify at first the function name exists
|
||||
if not instance.has_method(func_name):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("The function '%s' do not exists checked instance '%s'." % [func_name, instance])
|
||||
_interrupted = true
|
||||
else:
|
||||
_current_value_provider = CallBackValueProvider.new(instance, func_name, args)
|
||||
|
||||
|
||||
func _notification(_what :int) -> void:
|
||||
if is_instance_valid(_current_value_provider):
|
||||
_current_value_provider.dispose()
|
||||
_current_value_provider = null
|
||||
if is_instance_valid(_sleep_timer):
|
||||
Engine.get_main_loop().root.remove_child(_sleep_timer)
|
||||
_sleep_timer.stop()
|
||||
_sleep_timer.free()
|
||||
_sleep_timer = null
|
||||
func _notification(what :int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
_interrupted = true
|
||||
var main_node :Node = (Engine.get_main_loop() as SceneTree).root
|
||||
if is_instance_valid(_current_value_provider):
|
||||
_current_value_provider.dispose()
|
||||
_current_value_provider = null
|
||||
if is_instance_valid(_sleep_timer):
|
||||
_sleep_timer.set_wait_time(0.0001)
|
||||
_sleep_timer.stop()
|
||||
main_node.remove_child(_sleep_timer)
|
||||
_sleep_timer.free()
|
||||
_sleep_timer = null
|
||||
|
||||
|
||||
func report_success() -> GdUnitFuncAssert:
|
||||
|
@ -43,18 +49,14 @@ func report_success() -> GdUnitFuncAssert:
|
|||
return self
|
||||
|
||||
|
||||
func report_error(error_message :String) -> GdUnitFuncAssert:
|
||||
_current_error_message = error_message if _custom_failure_message == "" else _custom_failure_message
|
||||
GdAssertReports.report_error(_current_error_message, _line_number)
|
||||
func report_error(failure :String) -> GdUnitFuncAssert:
|
||||
_current_failure_message = GdAssertMessages.build_failure_message(failure, _additional_failure_message, _custom_failure_message)
|
||||
GdAssertReports.report_error(_current_failure_message, _line_number)
|
||||
return self
|
||||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _current_error_message
|
||||
|
||||
|
||||
func send_report(report :GdUnitReport)-> void:
|
||||
GdUnitSignals.instance().gdunit_report.emit(report)
|
||||
return _current_failure_message
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitFuncAssert:
|
||||
|
@ -62,6 +64,11 @@ func override_failure_message(message :String) -> GdUnitFuncAssert:
|
|||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitFuncAssert:
|
||||
_additional_failure_message = message
|
||||
return self
|
||||
|
||||
|
||||
func wait_until(timeout := 2000) -> GdUnitFuncAssert:
|
||||
if timeout <= 0:
|
||||
push_warning("Invalid timeout param, alloed timeouts must be grater than 0. Use default timeout instead")
|
||||
|
@ -111,6 +118,10 @@ func cb_is_equal(c :Variant, e :Variant) -> bool: return GdObjects.equals(c,e)
|
|||
func cb_is_not_equal(c :Variant, e :Variant) -> bool: return not GdObjects.equals(c, e)
|
||||
|
||||
|
||||
func do_interrupt() -> void:
|
||||
_interrupted = true
|
||||
|
||||
|
||||
func _validate_callback(predicate :Callable, expected :Variant = null) -> void:
|
||||
if _interrupted:
|
||||
return
|
||||
|
@ -118,16 +129,16 @@ func _validate_callback(predicate :Callable, expected :Variant = null) -> void:
|
|||
var time_scale := Engine.get_time_scale()
|
||||
var timer := Timer.new()
|
||||
timer.set_name("gdunit_funcassert_interrupt_timer_%d" % timer.get_instance_id())
|
||||
Engine.get_main_loop().root.add_child(timer)
|
||||
var scene_tree := Engine.get_main_loop() as SceneTree
|
||||
scene_tree.root.add_child(timer)
|
||||
timer.add_to_group("GdUnitTimers")
|
||||
timer.timeout.connect(func do_interrupt() -> void:
|
||||
_interrupted = true
|
||||
, CONNECT_DEFERRED)
|
||||
@warning_ignore("return_value_discarded")
|
||||
timer.timeout.connect(do_interrupt, CONNECT_DEFERRED)
|
||||
timer.set_one_shot(true)
|
||||
timer.start((_timeout/1000.0)*time_scale)
|
||||
_sleep_timer = Timer.new()
|
||||
_sleep_timer.set_name("gdunit_funcassert_sleep_timer_%d" % _sleep_timer.get_instance_id() )
|
||||
Engine.get_main_loop().root.add_child(_sleep_timer)
|
||||
scene_tree.root.add_child(_sleep_timer)
|
||||
|
||||
while true:
|
||||
var current :Variant = await next_current_value()
|
||||
|
@ -139,13 +150,20 @@ func _validate_callback(predicate :Callable, expected :Variant = null) -> void:
|
|||
await _sleep_timer.timeout
|
||||
|
||||
_sleep_timer.stop()
|
||||
await Engine.get_main_loop().process_frame
|
||||
await scene_tree.process_frame
|
||||
if _interrupted:
|
||||
# https://github.com/godotengine/godot/issues/73052
|
||||
#var predicate_name = predicate.get_method()
|
||||
var predicate_name :String = str(predicate).split('::')[1]
|
||||
report_error(GdAssertMessages.error_interrupted(predicate_name.strip_edges().trim_prefix("cb_"), expected, LocalTime.elapsed(_timeout)))
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error(GdAssertMessages.error_interrupted(
|
||||
predicate_name.strip_edges().trim_prefix("cb_"),
|
||||
expected,
|
||||
LocalTime.elapsed(_timeout)
|
||||
)
|
||||
)
|
||||
else:
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_success()
|
||||
_sleep_timer.free()
|
||||
timer.free()
|
||||
|
|
|
@ -17,6 +17,7 @@ func _init(callable :Callable) -> void:
|
|||
func _execute() -> Array[ErrorLogEntry]:
|
||||
# execute the given code and monitor for runtime errors
|
||||
if _callable == null or not _callable.is_valid():
|
||||
@warning_ignore("return_value_discarded")
|
||||
_report_error("Invalid Callable '%s'" % _callable)
|
||||
else:
|
||||
await _callable.call()
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
extends GdUnitIntAssert
|
||||
|
||||
var _base: GdUnitAssert
|
||||
var _base: GdUnitAssertImpl
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
_base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(current)
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if not GdUnitAssertions.validate_value_type(current, TYPE_INT):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitIntAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
|
||||
|
||||
|
||||
|
@ -34,30 +34,41 @@ func report_error(error :String) -> GdUnitIntAssert:
|
|||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _base._current_error_message
|
||||
return _base.failure_message()
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitIntAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitIntAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func is_null() -> GdUnitIntAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_not_null() -> GdUnitIntAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_equal(expected :int) -> GdUnitIntAssert:
|
||||
func is_equal(expected :Variant) -> GdUnitIntAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_equal(expected)
|
||||
return self
|
||||
|
||||
|
||||
func is_not_equal(expected :int) -> GdUnitIntAssert:
|
||||
func is_not_equal(expected :Variant) -> GdUnitIntAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_equal(expected)
|
||||
return self
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
extends GdUnitObjectAssert
|
||||
|
||||
var _base :GdUnitAssert
|
||||
var _base: GdUnitAssertImpl
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
_base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(current)
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if (current != null
|
||||
|
@ -13,6 +12,7 @@ func _init(current :Variant) -> void:
|
|||
or GdUnitAssertions.validate_value_type(current, TYPE_INT)
|
||||
or GdUnitAssertions.validate_value_type(current, TYPE_FLOAT)
|
||||
or GdUnitAssertions.validate_value_type(current, TYPE_STRING))):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitObjectAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
|
||||
|
||||
|
||||
|
@ -38,30 +38,41 @@ func report_error(error :String) -> GdUnitObjectAssert:
|
|||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _base._current_error_message
|
||||
return _base.failure_message()
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitObjectAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitObjectAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func is_equal(expected :Variant) -> GdUnitObjectAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_equal(expected)
|
||||
return self
|
||||
|
||||
|
||||
func is_not_equal(expected :Variant) -> GdUnitObjectAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_equal(expected)
|
||||
return self
|
||||
|
||||
|
||||
func is_null() -> GdUnitObjectAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_not_null() -> GdUnitObjectAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_null()
|
||||
return self
|
||||
|
||||
|
@ -70,19 +81,15 @@ func is_not_null() -> GdUnitObjectAssert:
|
|||
func is_same(expected :Variant) -> GdUnitObjectAssert:
|
||||
var current :Variant = current_value()
|
||||
if not is_same(current, expected):
|
||||
report_error(GdAssertMessages.error_is_same(current, expected))
|
||||
return self
|
||||
report_success()
|
||||
return self
|
||||
return report_error(GdAssertMessages.error_is_same(current, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_same(expected :Variant) -> GdUnitObjectAssert:
|
||||
var current :Variant = current_value()
|
||||
if is_same(current, expected):
|
||||
report_error(GdAssertMessages.error_not_same(current, expected))
|
||||
return self
|
||||
report_success()
|
||||
return self
|
||||
return report_error(GdAssertMessages.error_not_same(current, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_instanceof(type :Object) -> GdUnitObjectAssert:
|
||||
|
@ -90,10 +97,8 @@ func is_instanceof(type :Object) -> GdUnitObjectAssert:
|
|||
if current == null or not is_instance_of(current, type):
|
||||
var result_expected: = GdObjects.extract_class_name(type)
|
||||
var result_current: = GdObjects.extract_class_name(current)
|
||||
report_error(GdAssertMessages.error_is_instanceof(result_current, result_expected))
|
||||
return self
|
||||
report_success()
|
||||
return self
|
||||
return report_error(GdAssertMessages.error_is_instanceof(result_current, result_expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_instanceof(type :Variant) -> GdUnitObjectAssert:
|
||||
|
@ -101,9 +106,8 @@ func is_not_instanceof(type :Variant) -> GdUnitObjectAssert:
|
|||
if is_instance_of(current, type):
|
||||
var result: = GdObjects.extract_class_name(type)
|
||||
if result.is_success():
|
||||
report_error("Expected not be a instance of <%s>" % result.value())
|
||||
else:
|
||||
push_error("Internal ERROR: %s" % result.error_message())
|
||||
return report_error("Expected not be a instance of <%s>" % str(result.value()))
|
||||
|
||||
push_error("Internal ERROR: %s" % result.error_message())
|
||||
return self
|
||||
report_success()
|
||||
return self
|
||||
return report_success()
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
extends GdUnitResultAssert
|
||||
|
||||
var _base :GdUnitAssert
|
||||
var _base: GdUnitAssertImpl
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
_base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(current)
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if not validate_value_type(current):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitResultAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ func validate_value_type(value :Variant) -> bool:
|
|||
|
||||
|
||||
func current_value() -> GdUnitResult:
|
||||
return _base.current_value() as GdUnitResult
|
||||
return _base.current_value()
|
||||
|
||||
|
||||
func report_success() -> GdUnitResultAssert:
|
||||
|
@ -38,20 +38,29 @@ func report_error(error :String) -> GdUnitResultAssert:
|
|||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _base._current_error_message
|
||||
return _base.failure_message()
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitResultAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitResultAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func is_null() -> GdUnitResultAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_not_null() -> GdUnitResultAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_null()
|
||||
return self
|
||||
|
||||
|
@ -59,63 +68,50 @@ func is_not_null() -> GdUnitResultAssert:
|
|||
func is_empty() -> GdUnitResultAssert:
|
||||
var result := current_value()
|
||||
if result == null or not result.is_empty():
|
||||
report_error(GdAssertMessages.error_result_is_empty(result))
|
||||
else:
|
||||
report_success()
|
||||
return self
|
||||
return report_error(GdAssertMessages.error_result_is_empty(result))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_success() -> GdUnitResultAssert:
|
||||
var result := current_value()
|
||||
if result == null or not result.is_success():
|
||||
report_error(GdAssertMessages.error_result_is_success(result))
|
||||
else:
|
||||
report_success()
|
||||
return self
|
||||
return report_error(GdAssertMessages.error_result_is_success(result))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_warning() -> GdUnitResultAssert:
|
||||
var result := current_value()
|
||||
if result == null or not result.is_warn():
|
||||
report_error(GdAssertMessages.error_result_is_warning(result))
|
||||
else:
|
||||
report_success()
|
||||
return self
|
||||
return report_error(GdAssertMessages.error_result_is_warning(result))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_error() -> GdUnitResultAssert:
|
||||
var result := current_value()
|
||||
if result == null or not result.is_error():
|
||||
report_error(GdAssertMessages.error_result_is_error(result))
|
||||
else:
|
||||
report_success()
|
||||
return self
|
||||
return report_error(GdAssertMessages.error_result_is_error(result))
|
||||
return report_success()
|
||||
|
||||
|
||||
func contains_message(expected :String) -> GdUnitResultAssert:
|
||||
var result := current_value()
|
||||
if result == null:
|
||||
report_error(GdAssertMessages.error_result_has_message("<null>", expected))
|
||||
return self
|
||||
return report_error(GdAssertMessages.error_result_has_message("<null>", expected))
|
||||
if result.is_success():
|
||||
report_error(GdAssertMessages.error_result_has_message_on_success(expected))
|
||||
elif result.is_error() and result.error_message() != expected:
|
||||
report_error(GdAssertMessages.error_result_has_message(result.error_message(), expected))
|
||||
elif result.is_warn() and result.warn_message() != expected:
|
||||
report_error(GdAssertMessages.error_result_has_message(result.warn_message(), expected))
|
||||
else:
|
||||
report_success()
|
||||
return self
|
||||
return report_error(GdAssertMessages.error_result_has_message_on_success(expected))
|
||||
if result.is_error() and result.error_message() != expected:
|
||||
return report_error(GdAssertMessages.error_result_has_message(result.error_message(), expected))
|
||||
if result.is_warn() and result.warn_message() != expected:
|
||||
return report_error(GdAssertMessages.error_result_has_message(result.warn_message(), expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_value(expected :Variant) -> GdUnitResultAssert:
|
||||
var result := current_value()
|
||||
var value :Variant = null if result == null else result.value()
|
||||
if not GdObjects.equals(value, expected):
|
||||
report_error(GdAssertMessages.error_result_is_value(value, expected))
|
||||
else:
|
||||
report_success()
|
||||
return self
|
||||
return report_error(GdAssertMessages.error_result_is_value(value, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_equal(expected :Variant) -> GdUnitResultAssert:
|
||||
|
|
|
@ -4,8 +4,9 @@ const DEFAULT_TIMEOUT := 2000
|
|||
|
||||
var _signal_collector :GdUnitSignalCollector
|
||||
var _emitter :Object
|
||||
var _current_error_message :String = ""
|
||||
var _current_failure_message :String = ""
|
||||
var _custom_failure_message :String = ""
|
||||
var _additional_failure_message: String = ""
|
||||
var _line_number := -1
|
||||
var _timeout := DEFAULT_TIMEOUT
|
||||
var _interrupted := false
|
||||
|
@ -21,6 +22,14 @@ func _init(emitter :Object) -> void:
|
|||
GdAssertReports.reset_last_error_line_number()
|
||||
|
||||
|
||||
func _notification(what :int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
_interrupted = true
|
||||
if is_instance_valid(_emitter):
|
||||
_signal_collector.unregister_emitter(_emitter)
|
||||
_emitter = null
|
||||
|
||||
|
||||
func report_success() -> GdUnitAssert:
|
||||
GdAssertReports.report_success()
|
||||
return self
|
||||
|
@ -31,18 +40,14 @@ func report_warning(message :String) -> GdUnitAssert:
|
|||
return self
|
||||
|
||||
|
||||
func report_error(error_message :String) -> GdUnitAssert:
|
||||
_current_error_message = error_message if _custom_failure_message == "" else _custom_failure_message
|
||||
GdAssertReports.report_error(_current_error_message, _line_number)
|
||||
func report_error(failure :String) -> GdUnitAssert:
|
||||
_current_failure_message = GdAssertMessages.build_failure_message(failure, _additional_failure_message, _custom_failure_message)
|
||||
GdAssertReports.report_error(_current_failure_message, _line_number)
|
||||
return self
|
||||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _current_error_message
|
||||
|
||||
|
||||
func send_report(report :GdUnitReport)-> void:
|
||||
GdUnitSignals.instance().gdunit_report.emit(report)
|
||||
return _current_failure_message
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitSignalAssert:
|
||||
|
@ -50,8 +55,14 @@ func override_failure_message(message :String) -> GdUnitSignalAssert:
|
|||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitSignalAssert:
|
||||
_additional_failure_message = message
|
||||
return self
|
||||
|
||||
|
||||
func wait_until(timeout := 2000) -> GdUnitSignalAssert:
|
||||
if timeout <= 0:
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_warning("Invalid timeout parameter, allowed timeouts must be greater than 0, use default timeout instead!")
|
||||
_timeout = DEFAULT_TIMEOUT
|
||||
else:
|
||||
|
@ -62,6 +73,7 @@ func wait_until(timeout := 2000) -> GdUnitSignalAssert:
|
|||
# Verifies the signal exists checked the emitter
|
||||
func is_signal_exists(signal_name :String) -> GdUnitSignalAssert:
|
||||
if not _emitter.has_signal(signal_name):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("The signal '%s' not exists checked object '%s'." % [signal_name, _emitter.get_class()])
|
||||
return self
|
||||
|
||||
|
@ -80,29 +92,30 @@ func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert:
|
|||
|
||||
func _wail_until_signal(signal_name :String, expected_args :Array, expect_not_emitted: bool) -> GdUnitSignalAssert:
|
||||
if _emitter == null:
|
||||
report_error("Can't wait for signal checked a NULL object.")
|
||||
return self
|
||||
return report_error("Can't wait for signal checked a NULL object.")
|
||||
# first verify the signal is defined
|
||||
if not _emitter.has_signal(signal_name):
|
||||
report_error("Can't wait for non-existion signal '%s' checked object '%s'." % [signal_name,_emitter.get_class()])
|
||||
return self
|
||||
return report_error("Can't wait for non-existion signal '%s' checked object '%s'." % [signal_name,_emitter.get_class()])
|
||||
_signal_collector.register_emitter(_emitter)
|
||||
var time_scale := Engine.get_time_scale()
|
||||
var timer := Timer.new()
|
||||
Engine.get_main_loop().root.add_child(timer)
|
||||
(Engine.get_main_loop() as SceneTree).root.add_child(timer)
|
||||
timer.add_to_group("GdUnitTimers")
|
||||
timer.set_one_shot(true)
|
||||
@warning_ignore("return_value_discarded")
|
||||
timer.timeout.connect(func on_timeout() -> void: _interrupted = true)
|
||||
timer.start((_timeout/1000.0)*time_scale)
|
||||
var is_signal_emitted := false
|
||||
while not _interrupted and not is_signal_emitted:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
if is_instance_valid(_emitter):
|
||||
is_signal_emitted = _signal_collector.match(_emitter, signal_name, expected_args)
|
||||
if is_signal_emitted and expect_not_emitted:
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error(GdAssertMessages.error_signal_emitted(signal_name, expected_args, LocalTime.elapsed(int(_timeout-timer.time_left*1000))))
|
||||
|
||||
if _interrupted and not expect_not_emitted:
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error(GdAssertMessages.error_wait_signal(signal_name, expected_args, LocalTime.elapsed(_timeout)))
|
||||
timer.free()
|
||||
if is_instance_valid(_emitter):
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
extends GdUnitStringAssert
|
||||
|
||||
var _base :GdUnitAssert
|
||||
var _base: GdUnitAssertImpl
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
_base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(current)
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if current != null and typeof(current) != TYPE_STRING and typeof(current) != TYPE_STRING_NAME:
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitStringAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ func _notification(event :int) -> void:
|
|||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _base._current_error_message
|
||||
return _base.failure_message()
|
||||
|
||||
|
||||
func current_value() -> Variant:
|
||||
|
@ -38,16 +38,25 @@ func report_error(error :String) -> GdUnitStringAssert:
|
|||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitStringAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitStringAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func is_null() -> GdUnitStringAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_not_null() -> GdUnitStringAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_null()
|
||||
return self
|
||||
|
||||
|
@ -90,49 +99,56 @@ func is_not_equal_ignoring_case(expected :Variant) -> GdUnitStringAssert:
|
|||
|
||||
func is_empty() -> GdUnitStringAssert:
|
||||
var current :Variant = current_value()
|
||||
if current == null or not current.is_empty():
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current == null or not (current as String).is_empty():
|
||||
return report_error(GdAssertMessages.error_is_empty(current))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_empty() -> GdUnitStringAssert:
|
||||
var current :Variant = current_value()
|
||||
if current == null or current.is_empty():
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current == null or (current as String).is_empty():
|
||||
return report_error(GdAssertMessages.error_is_not_empty())
|
||||
return report_success()
|
||||
|
||||
|
||||
func contains(expected :String) -> GdUnitStringAssert:
|
||||
var current :Variant = current_value()
|
||||
if current == null or current.find(expected) == -1:
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current == null or (current as String).find(expected) == -1:
|
||||
return report_error(GdAssertMessages.error_contains(current, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func not_contains(expected :String) -> GdUnitStringAssert:
|
||||
var current :Variant = current_value()
|
||||
if current != null and current.find(expected) != -1:
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current != null and (current as String).find(expected) != -1:
|
||||
return report_error(GdAssertMessages.error_not_contains(current, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func contains_ignoring_case(expected :String) -> GdUnitStringAssert:
|
||||
var current :Variant = current_value()
|
||||
if current == null or current.findn(expected) == -1:
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current == null or (current as String).findn(expected) == -1:
|
||||
return report_error(GdAssertMessages.error_contains_ignoring_case(current, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func not_contains_ignoring_case(expected :String) -> GdUnitStringAssert:
|
||||
var current :Variant = current_value()
|
||||
if current != null and current.findn(expected) != -1:
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current != null and (current as String).findn(expected) != -1:
|
||||
return report_error(GdAssertMessages.error_not_contains_ignoring_case(current, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func starts_with(expected :String) -> GdUnitStringAssert:
|
||||
var current :Variant = current_value()
|
||||
if current == null or current.find(expected) != 0:
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current == null or (current as String).find(expected) != 0:
|
||||
return report_error(GdAssertMessages.error_starts_with(current, expected))
|
||||
return report_success()
|
||||
|
||||
|
@ -141,8 +157,10 @@ func ends_with(expected :String) -> GdUnitStringAssert:
|
|||
var current :Variant = current_value()
|
||||
if current == null:
|
||||
return report_error(GdAssertMessages.error_ends_with(current, expected))
|
||||
var find :int = current.length() - expected.length()
|
||||
if current.rfind(expected) != find:
|
||||
@warning_ignore("unsafe_cast")
|
||||
var find :int = (current as String).length() - expected.length()
|
||||
@warning_ignore("unsafe_cast")
|
||||
if (current as String).rfind(expected) != find:
|
||||
return report_error(GdAssertMessages.error_ends_with(current, expected))
|
||||
return report_success()
|
||||
|
||||
|
@ -152,22 +170,23 @@ func has_length(expected :int, comparator := Comparator.EQUAL) -> GdUnitStringAs
|
|||
var current :Variant = current_value()
|
||||
if current == null:
|
||||
return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
|
||||
var str_current: String = current
|
||||
match comparator:
|
||||
Comparator.EQUAL:
|
||||
if current.length() != expected:
|
||||
return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
|
||||
if str_current.length() != expected:
|
||||
return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
|
||||
Comparator.LESS_THAN:
|
||||
if current.length() >= expected:
|
||||
return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
|
||||
if str_current.length() >= expected:
|
||||
return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
|
||||
Comparator.LESS_EQUAL:
|
||||
if current.length() > expected:
|
||||
return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
|
||||
if str_current.length() > expected:
|
||||
return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
|
||||
Comparator.GREATER_THAN:
|
||||
if current.length() <= expected:
|
||||
return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
|
||||
if str_current.length() <= expected:
|
||||
return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
|
||||
Comparator.GREATER_EQUAL:
|
||||
if current.length() < expected:
|
||||
return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
|
||||
if str_current.length() < expected:
|
||||
return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
|
||||
_:
|
||||
return report_error("Comparator '%d' not implemented!" % comparator)
|
||||
return report_success()
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
extends GdUnitVectorAssert
|
||||
|
||||
var _base: GdUnitAssert
|
||||
var _current_type :int
|
||||
var _base: GdUnitAssertImpl
|
||||
var _current_type: int
|
||||
var _type_check: bool
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
_base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
|
||||
ResourceLoader.CACHE_MODE_REUSE).new(current)
|
||||
func _init(current: Variant, type_check := true) -> void:
|
||||
_type_check = type_check
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if not _validate_value_type(current):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitVectorAssert error, the type <%s> is not supported." % GdObjects.typeof_as_string(current))
|
||||
_current_type = typeof(current)
|
||||
|
||||
|
@ -39,6 +40,7 @@ func _validate_is_vector_type(value :Variant) -> bool:
|
|||
var type := typeof(value)
|
||||
if type == _current_type or _current_type == TYPE_NIL:
|
||||
return true
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error(GdAssertMessages.error_is_wrong_type(_current_type, type))
|
||||
return false
|
||||
|
||||
|
@ -58,34 +60,45 @@ func report_error(error :String) -> GdUnitVectorAssert:
|
|||
|
||||
|
||||
func failure_message() -> String:
|
||||
return _base._current_error_message
|
||||
return _base.failure_message()
|
||||
|
||||
|
||||
func override_failure_message(message :String) -> GdUnitVectorAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message :String) -> GdUnitVectorAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func is_null() -> GdUnitVectorAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_not_null() -> GdUnitVectorAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_equal(expected :Variant) -> GdUnitVectorAssert:
|
||||
if not _validate_is_vector_type(expected):
|
||||
func is_equal(expected: Variant) -> GdUnitVectorAssert:
|
||||
if _type_check and not _validate_is_vector_type(expected):
|
||||
return self
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_equal(expected)
|
||||
return self
|
||||
|
||||
|
||||
func is_not_equal(expected :Variant) -> GdUnitVectorAssert:
|
||||
if not _validate_is_vector_type(expected):
|
||||
func is_not_equal(expected: Variant) -> GdUnitVectorAssert:
|
||||
if _type_check and not _validate_is_vector_type(expected):
|
||||
return self
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_equal(expected)
|
||||
return self
|
||||
|
||||
|
|
|
@ -4,3 +4,7 @@ extends RefCounted
|
|||
|
||||
func get_value() -> Variant:
|
||||
return null
|
||||
|
||||
|
||||
func dispose() -> void:
|
||||
pass
|
||||
|
|
|
@ -40,22 +40,23 @@ func options() -> CmdOptions:
|
|||
return _options
|
||||
|
||||
|
||||
func _parse_cmd_arguments(option :CmdOption, args :Array) -> int:
|
||||
func _parse_cmd_arguments(option: CmdOption, args: Array) -> int:
|
||||
var command_name := option.short_command()
|
||||
var command :CmdCommand = _parsed_commands.get(command_name, CmdCommand.new(command_name))
|
||||
var command: CmdCommand = _parsed_commands.get(command_name, CmdCommand.new(command_name))
|
||||
|
||||
if option.has_argument():
|
||||
if not option.is_argument_optional() and args.is_empty():
|
||||
return -1
|
||||
if _is_next_value_argument(args):
|
||||
command.add_argument(args.pop_front())
|
||||
var value: String = args.pop_front()
|
||||
command.add_argument(value)
|
||||
elif not option.is_argument_optional():
|
||||
return -1
|
||||
_parsed_commands[command_name] = command
|
||||
return 0
|
||||
|
||||
|
||||
func _is_next_value_argument(args :Array) -> bool:
|
||||
func _is_next_value_argument(args: PackedStringArray) -> bool:
|
||||
if args.is_empty():
|
||||
return false
|
||||
return _options.get_option(args[0]) == null
|
||||
|
|
|
@ -19,6 +19,7 @@ func arguments() -> PackedStringArray:
|
|||
|
||||
|
||||
func add_argument(arg :String) -> void:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_arguments.append(arg)
|
||||
|
||||
|
||||
|
|
|
@ -57,14 +57,17 @@ func _validate() -> GdUnitResult:
|
|||
if _command_cbs[cmd_name][CB_SINGLE_ARG]
|
||||
else _command_cbs[cmd_name][CB_MULTI_ARGS])
|
||||
if cb != NO_CB and not cb.is_valid():
|
||||
@warning_ignore("return_value_discarded")
|
||||
errors.append("Invalid function reference for command '%s', Check the function reference!" % cmd_name)
|
||||
if _cmd_options.get_option(cmd_name) == null:
|
||||
@warning_ignore("return_value_discarded")
|
||||
errors.append("The command '%s' is unknown, verify your CmdOptions!" % cmd_name)
|
||||
# verify for multiple registered command callbacks
|
||||
if _enhanced_fr_test and cb != NO_CB:
|
||||
var cb_method: = cb.get_method()
|
||||
if registered_cbs.has(cb_method):
|
||||
var already_registered_cmd :String = registered_cbs[cb_method]
|
||||
@warning_ignore("return_value_discarded")
|
||||
errors.append("The function reference '%s' already registerd for command '%s'!" % [cb_method, already_registered_cmd])
|
||||
else:
|
||||
registered_cbs[cb_method] = cmd_name
|
||||
|
@ -95,7 +98,8 @@ func execute(commands :Array[CmdCommand]) -> GdUnitResult:
|
|||
# we need to find the method and determin the arguments to call the right function
|
||||
for m in cb_m.get_object().get_method_list():
|
||||
if m["name"] == cb_m.get_method():
|
||||
if m["args"].size() > 1:
|
||||
@warning_ignore("unsafe_cast")
|
||||
if (m["args"] as Array).size() > 1:
|
||||
cb_m.callv(arguments)
|
||||
break
|
||||
else:
|
||||
|
|
|
@ -23,14 +23,14 @@ var _color_mode := COLOR_TABLE
|
|||
|
||||
func color(p_color :Color) -> CmdConsole:
|
||||
# using color table 16 - 231 a 6 x 6 x 6 RGB color cube (16 + R * 36 + G * 6 + B)
|
||||
if _color_mode == COLOR_TABLE:
|
||||
@warning_ignore("integer_division")
|
||||
var c2 := 16 + (int(p_color.r8/42) * 36) + (int(p_color.g8/42) * 6) + int(p_color.b8/42)
|
||||
if _debug_show_color_codes:
|
||||
printraw("%6d" % [c2])
|
||||
printraw("[38;5;%dm" % c2 )
|
||||
else:
|
||||
printraw("[38;2;%d;%d;%dm" % [p_color.r8, p_color.g8, p_color.b8] )
|
||||
#if _color_mode == COLOR_TABLE:
|
||||
# @warning_ignore("integer_division")
|
||||
# var c2 := 16 + (int(p_color.r8/42) * 36) + (int(p_color.g8/42) * 6) + int(p_color.b8/42)
|
||||
# if _debug_show_color_codes:
|
||||
# printraw("%6d" % [c2])
|
||||
# printraw("[38;5;%dm" % c2 )
|
||||
#else:
|
||||
printraw("[38;2;%d;%d;%dm" % [p_color.r8, p_color.g8, p_color.b8] )
|
||||
return self
|
||||
|
||||
|
||||
|
@ -59,6 +59,7 @@ func scroll_area(from :int, to :int) -> CmdConsole:
|
|||
return self
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func progress_bar(p_progress :int, p_color :Color = Color.POWDER_BLUE) -> CmdConsole:
|
||||
if p_progress < 0:
|
||||
p_progress = 0
|
||||
|
@ -123,6 +124,7 @@ func print_color(p_message :String, p_color :Color, p_flags := 0) -> CmdConsole:
|
|||
.end_color()
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func print_color_table() -> void:
|
||||
prints_color("Color Table 6x6x6", Color.ANTIQUE_WHITE)
|
||||
_debug_show_color_codes = true
|
||||
|
|
|
@ -28,10 +28,11 @@ static func is_type_array(type :int) -> bool:
|
|||
|
||||
## Filters an array by given value[br]
|
||||
## If the given value not an array it returns null, will remove all occurence of given value.
|
||||
static func filter_value(array :Variant, value :Variant) -> Variant:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
static func filter_value(array: Variant, value: Variant) -> Variant:
|
||||
if not is_array_type(array):
|
||||
return null
|
||||
var filtered_array :Variant = array.duplicate()
|
||||
var filtered_array: Variant = array.duplicate()
|
||||
var index :int = filtered_array.find(value)
|
||||
while index != -1:
|
||||
filtered_array.remove_at(index)
|
||||
|
@ -72,13 +73,12 @@ static func scan_typed(array :Array) -> int:
|
|||
## # will result in PackedString(["a", "b"])
|
||||
## GdArrayTools.as_string(PackedColorArray(Color.RED, COLOR.GREEN))
|
||||
## [/codeblock]
|
||||
static func as_string(elements :Variant, encode_value := true) -> String:
|
||||
if not is_array_type(elements):
|
||||
return "ERROR: Not an Array Type!"
|
||||
static func as_string(elements: Variant, encode_value := true) -> String:
|
||||
var delemiter := ", "
|
||||
if elements == null:
|
||||
return "<null>"
|
||||
if elements.is_empty():
|
||||
@warning_ignore("unsafe_cast")
|
||||
if (elements as Array).is_empty():
|
||||
return "<empty>"
|
||||
var prefix := _typeof_as_string(elements) if encode_value else ""
|
||||
var formatted := ""
|
||||
|
@ -93,6 +93,14 @@ static func as_string(elements :Variant, encode_value := true) -> String:
|
|||
return prefix + "[" + formatted + "]"
|
||||
|
||||
|
||||
static func has_same_content(current: Array, other: Array) -> bool:
|
||||
if current.size() != other.size(): return false
|
||||
for element: Variant in current:
|
||||
if not other.has(element): return false
|
||||
if current.count(element) != other.count(element): return false
|
||||
return true
|
||||
|
||||
|
||||
static func _typeof_as_string(value :Variant) -> String:
|
||||
var type := typeof(value)
|
||||
# for untyped array we retun empty string
|
||||
|
|
|
@ -7,7 +7,7 @@ const DIV_ADD :int = 214
|
|||
const DIV_SUB :int = 215
|
||||
|
||||
|
||||
static func _diff(lb: PackedByteArray, rb: PackedByteArray, lookup: Array, ldiff: Array, rdiff: Array) -> void:
|
||||
static func _diff(lb: PackedByteArray, rb: PackedByteArray, lookup: Array[Array], ldiff: Array, rdiff: Array) -> void:
|
||||
var loffset := lb.size()
|
||||
var roffset := rb.size()
|
||||
|
||||
|
@ -39,17 +39,19 @@ static func _diff(lb: PackedByteArray, rb: PackedByteArray, lookup: Array, ldiff
|
|||
|
||||
|
||||
# lookup[i][j] stores the length of LCS of substring X[0..i-1], Y[0..j-1]
|
||||
static func _createLookUp(lb: PackedByteArray, rb: PackedByteArray) -> Array:
|
||||
var lookup := Array()
|
||||
static func _createLookUp(lb: PackedByteArray, rb: PackedByteArray) -> Array[Array]:
|
||||
var lookup: Array[Array] = []
|
||||
@warning_ignore("return_value_discarded")
|
||||
lookup.resize(lb.size() + 1)
|
||||
for i in lookup.size():
|
||||
var x := []
|
||||
@warning_ignore("return_value_discarded")
|
||||
x.resize(rb.size() + 1)
|
||||
lookup[i] = x
|
||||
return lookup
|
||||
|
||||
|
||||
static func _buildLookup(lb: PackedByteArray, rb: PackedByteArray) -> Array:
|
||||
static func _buildLookup(lb: PackedByteArray, rb: PackedByteArray) -> Array[Array]:
|
||||
var lookup := _createLookUp(lb, rb)
|
||||
# first column of the lookup table will be all 0
|
||||
for i in lookup.size():
|
||||
|
@ -105,6 +107,7 @@ static func longestCommonSubsequence(text1 :String, text2 :String) -> PackedStri
|
|||
var lcsResultList := PackedStringArray();
|
||||
while (i < text1WordCount && j < text2WordCount):
|
||||
if text1Words[i] == text2Words[j]:
|
||||
@warning_ignore("return_value_discarded")
|
||||
lcsResultList.append(text2Words[j])
|
||||
i += 1
|
||||
j += 1
|
||||
|
|
|
@ -39,6 +39,8 @@ const DEFAULT_TYPED_RETURN_VALUES := {
|
|||
TYPE_PACKED_STRING_ARRAY: "PackedStringArray()",
|
||||
TYPE_PACKED_VECTOR2_ARRAY: "PackedVector2Array()",
|
||||
TYPE_PACKED_VECTOR3_ARRAY: "PackedVector3Array()",
|
||||
# since Godot 4.3.beta1 TYPE_PACKED_VECTOR4_ARRAY = 38
|
||||
GdObjects.TYPE_PACKED_VECTOR4_ARRAY: "PackedVector4Array()",
|
||||
TYPE_PACKED_COLOR_ARRAY: "PackedColorArray()",
|
||||
GdObjects.TYPE_VARIANT: "null",
|
||||
GdObjects.TYPE_ENUM: "0"
|
||||
|
@ -80,7 +82,9 @@ static func get_enum_default(value :String) -> Variant:
|
|||
return %s.values()[0]
|
||||
|
||||
""".dedent() % value
|
||||
@warning_ignore("return_value_discarded")
|
||||
script.reload()
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return script.new().call("get_enum_default")
|
||||
|
||||
|
||||
|
@ -111,20 +115,19 @@ func _init(push_errors :bool = false) -> void:
|
|||
|
||||
|
||||
@warning_ignore("unused_parameter")
|
||||
func get_template(return_type :Variant, is_vararg :bool) -> String:
|
||||
push_error("Must be implemented!")
|
||||
func get_template(return_type: GdFunctionDescriptor, is_callable: bool) -> String:
|
||||
assert(false, "'get_template' must be implemented!")
|
||||
return ""
|
||||
|
||||
func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray:
|
||||
var func_signature := func_descriptor.typeless()
|
||||
|
||||
func double(func_descriptor: GdFunctionDescriptor, is_callable: bool = false) -> PackedStringArray:
|
||||
var is_static := func_descriptor.is_static()
|
||||
var is_vararg := func_descriptor.is_vararg()
|
||||
var is_coroutine := func_descriptor.is_coroutine()
|
||||
var func_name := func_descriptor.name()
|
||||
var args := func_descriptor.args()
|
||||
var varargs := func_descriptor.varargs()
|
||||
var return_value := GdFunctionDoubler.default_return_value(func_descriptor)
|
||||
var arg_names := extract_arg_names(args)
|
||||
var arg_names := extract_arg_names(args, true)
|
||||
var vararg_names := extract_arg_names(varargs)
|
||||
|
||||
# save original constructor arguments
|
||||
|
@ -133,17 +136,15 @@ func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray:
|
|||
var constructor := "func _init(%s) -> void:\n super(%s)\n pass\n" % [constructor_args, ", ".join(arg_names)]
|
||||
return constructor.split("\n")
|
||||
|
||||
var double_src := ""
|
||||
double_src += '@warning_ignore("untyped_declaration")\n' if Engine.get_version_info().hex >= 0x40200 else '\n'
|
||||
var double_src := "@warning_ignore('shadowed_variable', 'untyped_declaration', 'unsafe_call_argument', 'unsafe_method_access')\n"
|
||||
if func_descriptor.is_engine():
|
||||
double_src += '@warning_ignore("native_method_override")\n'
|
||||
if func_descriptor.return_type() == GdObjects.TYPE_ENUM:
|
||||
double_src += '@warning_ignore("int_as_enum_without_match")\n'
|
||||
double_src += '@warning_ignore("int_as_enum_without_cast")\n'
|
||||
double_src += '@warning_ignore("shadowed_variable")\n'
|
||||
double_src += func_signature
|
||||
double_src += GdFunctionDoubler.extract_func_signature(func_descriptor)
|
||||
# fix to unix format, this is need when the template is edited under windows than the template is stored with \r\n
|
||||
var func_template := get_template(func_descriptor.return_type(), is_vararg).replace("\r\n", "\n")
|
||||
var func_template := get_template(func_descriptor, is_callable).replace("\r\n", "\n")
|
||||
double_src += func_template\
|
||||
.replace("$(arguments)", ", ".join(arg_names))\
|
||||
.replace("$(varargs)", ", ".join(vararg_names))\
|
||||
|
@ -159,25 +160,54 @@ func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray:
|
|||
return double_src.split("\n")
|
||||
|
||||
|
||||
func extract_arg_names(argument_signatures :Array[GdFunctionArgument]) -> PackedStringArray:
|
||||
func extract_arg_names(argument_signatures: Array[GdFunctionArgument], add_suffix := false) -> PackedStringArray:
|
||||
var arg_names := PackedStringArray()
|
||||
for arg in argument_signatures:
|
||||
arg_names.append(arg._name)
|
||||
@warning_ignore("return_value_discarded")
|
||||
arg_names.append(arg._name + ("_" if add_suffix else ""))
|
||||
return arg_names
|
||||
|
||||
|
||||
static func extract_constructor_args(args :Array[GdFunctionArgument]) -> PackedStringArray:
|
||||
var constructor_args := PackedStringArray()
|
||||
for arg in args:
|
||||
var arg_name := arg._name
|
||||
var arg_name := arg._name + "_"
|
||||
var default_value := get_default(arg)
|
||||
if default_value == "null":
|
||||
@warning_ignore("return_value_discarded")
|
||||
constructor_args.append(arg_name + ":Variant=" + default_value)
|
||||
else:
|
||||
@warning_ignore("return_value_discarded")
|
||||
constructor_args.append(arg_name + ":=" + default_value)
|
||||
return constructor_args
|
||||
|
||||
|
||||
static func extract_func_signature(descriptor: GdFunctionDescriptor) -> String:
|
||||
var func_signature := ""
|
||||
if descriptor._return_type == TYPE_NIL:
|
||||
func_signature = "func %s(%s) -> void:" % [descriptor.name(), typeless_args(descriptor)]
|
||||
elif descriptor._return_type == GdObjects.TYPE_VARIANT:
|
||||
func_signature = "func %s(%s):" % [descriptor.name(), typeless_args(descriptor)]
|
||||
else:
|
||||
func_signature = "func %s(%s) -> %s:" % [descriptor.name(), typeless_args(descriptor), descriptor.return_type_as_string()]
|
||||
return "static " + func_signature if descriptor.is_static() else func_signature
|
||||
|
||||
|
||||
static func typeless_args(descriptor: GdFunctionDescriptor) -> String:
|
||||
var collect := PackedStringArray()
|
||||
for arg in descriptor.args():
|
||||
if arg.has_default():
|
||||
@warning_ignore("return_value_discarded")
|
||||
collect.push_back(arg.name() + "_" + "=" + arg.value_as_string())
|
||||
else:
|
||||
@warning_ignore("return_value_discarded")
|
||||
collect.push_back(arg.name() + "_")
|
||||
for arg in descriptor.varargs():
|
||||
@warning_ignore("return_value_discarded")
|
||||
collect.push_back(arg.name() + "=" + arg.value_as_string())
|
||||
return ", ".join(collect)
|
||||
|
||||
|
||||
static func get_default(arg :GdFunctionArgument) -> String:
|
||||
if arg.has_default():
|
||||
return arg.value_as_string()
|
||||
|
|
|
@ -4,17 +4,20 @@ extends Resource
|
|||
|
||||
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
|
||||
const TYPE_VOID = TYPE_MAX + 1000
|
||||
const TYPE_VARARG = TYPE_MAX + 1001
|
||||
const TYPE_VARIANT = TYPE_MAX + 1002
|
||||
const TYPE_FUNC = TYPE_MAX + 1003
|
||||
const TYPE_FUZZER = TYPE_MAX + 1004
|
||||
|
||||
const TYPE_NODE = TYPE_MAX + 2001
|
||||
# introduced with Godot 4.3.beta1
|
||||
const TYPE_PACKED_VECTOR4_ARRAY = 38 #TYPE_PACKED_VECTOR4_ARRAY
|
||||
|
||||
const TYPE_VOID = 1000
|
||||
const TYPE_VARARG = 1001
|
||||
const TYPE_VARIANT = 1002
|
||||
const TYPE_FUNC = 1003
|
||||
const TYPE_FUZZER = 1004
|
||||
# missing Godot types
|
||||
const TYPE_CONTROL = TYPE_MAX + 2002
|
||||
const TYPE_CANVAS = TYPE_MAX + 2003
|
||||
const TYPE_ENUM = TYPE_MAX + 2004
|
||||
const TYPE_NODE = 2001
|
||||
const TYPE_CONTROL = 2002
|
||||
const TYPE_CANVAS = 2003
|
||||
const TYPE_ENUM = 2004
|
||||
|
||||
|
||||
# used as default value for varargs
|
||||
|
@ -59,6 +62,7 @@ const TYPE_AS_STRING_MAPPINGS := {
|
|||
TYPE_PACKED_STRING_ARRAY: "PackedStringArray",
|
||||
TYPE_PACKED_VECTOR2_ARRAY: "PackedVector2Array",
|
||||
TYPE_PACKED_VECTOR3_ARRAY: "PackedVector3Array",
|
||||
TYPE_PACKED_VECTOR4_ARRAY: "PackedVector4Array",
|
||||
TYPE_PACKED_COLOR_ARRAY: "PackedColorArray",
|
||||
TYPE_VOID: "void",
|
||||
TYPE_VARARG: "VarArg",
|
||||
|
@ -141,7 +145,8 @@ enum COMPARE_MODE {
|
|||
|
||||
|
||||
# prototype of better object to dictionary
|
||||
static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary:
|
||||
@warning_ignore("unsafe_cast")
|
||||
static func obj2dict(obj: Object, hashed_objects := Dictionary()) -> Dictionary:
|
||||
if obj == null:
|
||||
return {}
|
||||
var clazz_name := obj.get_class()
|
||||
|
@ -149,13 +154,20 @@ static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary:
|
|||
var clazz_path := ""
|
||||
|
||||
if is_instance_valid(obj) and obj.get_script() != null:
|
||||
var d := inst_to_dict(obj)
|
||||
clazz_path = d["@path"]
|
||||
if d["@subpath"] != NodePath(""):
|
||||
clazz_name = d["@subpath"]
|
||||
dict["@inner_class"] = true
|
||||
var script: Script = obj.get_script()
|
||||
# handle build-in scripts
|
||||
if script.resource_path != null and script.resource_path.contains(".tscn"):
|
||||
var path_elements := script.resource_path.split(".tscn")
|
||||
clazz_name = path_elements[0].get_file()
|
||||
clazz_path = script.resource_path
|
||||
else:
|
||||
clazz_name = clazz_path.get_file().replace(".gd", "")
|
||||
var d := inst_to_dict(obj)
|
||||
clazz_path = d["@path"]
|
||||
if d["@subpath"] != NodePath(""):
|
||||
clazz_name = d["@subpath"]
|
||||
dict["@inner_class"] = true
|
||||
else:
|
||||
clazz_name = clazz_path.get_file().replace(".gd", "")
|
||||
dict["@path"] = clazz_path
|
||||
|
||||
for property in obj.get_property_list():
|
||||
|
@ -173,11 +185,14 @@ static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary:
|
|||
dict[property_name] = str(property_value)
|
||||
continue
|
||||
hashed_objects[obj] = true
|
||||
dict[property_name] = obj2dict(property_value, hashed_objects)
|
||||
dict[property_name] = obj2dict(property_value as Object, hashed_objects)
|
||||
else:
|
||||
dict[property_name] = property_value
|
||||
if obj.has_method("get_children"):
|
||||
var childrens :Array = obj.get_children()
|
||||
if obj is Node:
|
||||
var childrens :Array = (obj as Node).get_children()
|
||||
dict["childrens"] = childrens.map(func (child :Object) -> Dictionary: return obj2dict(child, hashed_objects))
|
||||
if obj is TreeItem:
|
||||
var childrens :Array = (obj as TreeItem).get_children()
|
||||
dict["childrens"] = childrens.map(func (child :Object) -> Dictionary: return obj2dict(child, hashed_objects))
|
||||
|
||||
return {"%s" % clazz_name : dict}
|
||||
|
@ -187,14 +202,15 @@ static func equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool = false,
|
|||
return _equals(obj_a, obj_b, case_sensitive, compare_mode, [], 0)
|
||||
|
||||
|
||||
static func equals_sorted(obj_a :Array, obj_b :Array, case_sensitive :bool = false, compare_mode :COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
|
||||
var a := obj_a.duplicate()
|
||||
var b := obj_b.duplicate()
|
||||
static func equals_sorted(obj_a: Array[Variant], obj_b: Array[Variant], case_sensitive: bool = false, compare_mode: COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
|
||||
var a: Array[Variant] = obj_a.duplicate()
|
||||
var b: Array[Variant] = obj_b.duplicate()
|
||||
a.sort()
|
||||
b.sort()
|
||||
return equals(a, b, case_sensitive, compare_mode)
|
||||
|
||||
|
||||
@warning_ignore("unsafe_method_access", "unsafe_cast")
|
||||
static func _equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool, compare_mode :COMPARE_MODE, deep_stack :Array, stack_depth :int ) -> bool:
|
||||
var type_a := typeof(obj_a)
|
||||
var type_b := typeof(obj_b)
|
||||
|
@ -234,8 +250,8 @@ static func _equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool, compar
|
|||
return false
|
||||
if obj_a.get_class() != obj_b.get_class():
|
||||
return false
|
||||
var a := obj2dict(obj_a)
|
||||
var b := obj2dict(obj_b)
|
||||
var a := obj2dict(obj_a as Object)
|
||||
var b := obj2dict(obj_b as Object)
|
||||
return _equals(a, b, case_sensitive, compare_mode, deep_stack, stack_depth)
|
||||
return obj_a == obj_b
|
||||
|
||||
|
@ -293,6 +309,7 @@ static func to_pascal_case(value :String) -> String:
|
|||
return value.capitalize().replace(" ", "")
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
static func to_snake_case(value :String) -> String:
|
||||
var result := PackedStringArray()
|
||||
for ch in value:
|
||||
|
@ -313,6 +330,8 @@ static func is_snake_case(value :String) -> bool:
|
|||
|
||||
|
||||
static func type_as_string(type :int) -> String:
|
||||
if type < TYPE_MAX:
|
||||
return type_string(type)
|
||||
return TYPE_AS_STRING_MAPPINGS.get(type, "Variant")
|
||||
|
||||
|
||||
|
@ -345,10 +364,13 @@ static func _is_type_equivalent(type_a :int, type_b :int) -> bool:
|
|||
or type_a == type_b)
|
||||
|
||||
|
||||
static func is_engine_type(value :Object) -> bool:
|
||||
static func is_engine_type(value :Variant) -> bool:
|
||||
if value is GDScript or value is ScriptExtension:
|
||||
return false
|
||||
return value.is_class("GDScriptNativeClass")
|
||||
var obj: Object = value
|
||||
if is_instance_valid(obj) and obj.has_method("is_class"):
|
||||
return obj.is_class("GDScriptNativeClass")
|
||||
return false
|
||||
|
||||
|
||||
static func is_type(value :Variant) -> bool:
|
||||
|
@ -359,7 +381,8 @@ static func is_type(value :Variant) -> bool:
|
|||
if is_engine_type(value):
|
||||
return true
|
||||
# is a custom class type
|
||||
if value is GDScript and value.can_instantiate():
|
||||
@warning_ignore("unsafe_cast")
|
||||
if value is GDScript and (value as GDScript).can_instantiate():
|
||||
return true
|
||||
return false
|
||||
|
||||
|
@ -372,7 +395,8 @@ static func _is_same(left :Variant, right :Variant) -> bool:
|
|||
if left_type != right_type:
|
||||
return false
|
||||
if left_type == TYPE_OBJECT and right_type == TYPE_OBJECT:
|
||||
return left.get_instance_id() == right.get_instance_id()
|
||||
@warning_ignore("unsafe_cast")
|
||||
return (left as Object).get_instance_id() == (right as Object).get_instance_id()
|
||||
return equals(left, right)
|
||||
|
||||
|
||||
|
@ -397,7 +421,8 @@ static func is_scene(value :Variant) -> bool:
|
|||
|
||||
|
||||
static func is_scene_resource_path(value :Variant) -> bool:
|
||||
return value is String and value.ends_with(".tscn")
|
||||
@warning_ignore("unsafe_cast")
|
||||
return value is String and (value as String).ends_with(".tscn")
|
||||
|
||||
|
||||
static func is_gd_script(script :Script) -> bool:
|
||||
|
@ -413,8 +438,8 @@ static func is_gd_testsuite(script :Script) -> bool:
|
|||
if is_gd_script(script):
|
||||
var stack := [script]
|
||||
while not stack.is_empty():
|
||||
var current := stack.pop_front() as Script
|
||||
var base := current.get_base_script() as Script
|
||||
var current: Script = stack.pop_front()
|
||||
var base: Script = current.get_base_script()
|
||||
if base != null:
|
||||
if base.resource_path.find("GdUnitTestSuite") != -1:
|
||||
return true
|
||||
|
@ -422,11 +447,12 @@ static func is_gd_testsuite(script :Script) -> bool:
|
|||
return false
|
||||
|
||||
|
||||
static func is_singleton(value :Variant) -> bool:
|
||||
static func is_singleton(value: Variant) -> bool:
|
||||
if not is_instance_valid(value) or is_native_class(value):
|
||||
return false
|
||||
for name in Engine.get_singleton_list():
|
||||
if value.is_class(name):
|
||||
@warning_ignore("unsafe_cast")
|
||||
if (value as Object).is_class(name):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
@ -434,17 +460,19 @@ static func is_singleton(value :Variant) -> bool:
|
|||
static func is_instance(value :Variant) -> bool:
|
||||
if not is_instance_valid(value) or is_native_class(value):
|
||||
return false
|
||||
if is_script(value) and value.get_instance_base_type() == "":
|
||||
@warning_ignore("unsafe_cast")
|
||||
if is_script(value) and (value as Script).get_instance_base_type() == "":
|
||||
return true
|
||||
if is_scene(value):
|
||||
return true
|
||||
return not value.has_method('new') and not value.has_method('instance')
|
||||
@warning_ignore("unsafe_cast")
|
||||
return not (value as Object).has_method('new') and not (value as Object).has_method('instance')
|
||||
|
||||
|
||||
# only object form type Node and attached filename
|
||||
static func is_instance_scene(instance :Variant) -> bool:
|
||||
if instance is Node:
|
||||
var node := instance as Node
|
||||
var node: Node = instance
|
||||
return node.get_scene_file_path() != null and not node.get_scene_file_path().is_empty()
|
||||
return false
|
||||
|
||||
|
@ -452,7 +480,8 @@ static func is_instance_scene(instance :Variant) -> bool:
|
|||
static func can_be_instantiate(obj :Variant) -> bool:
|
||||
if not obj or is_engine_type(obj):
|
||||
return false
|
||||
return obj.has_method("new")
|
||||
@warning_ignore("unsafe_cast")
|
||||
return (obj as Object).has_method("new")
|
||||
|
||||
|
||||
static func create_instance(clazz :Variant) -> GdUnitResult:
|
||||
|
@ -461,48 +490,54 @@ static func create_instance(clazz :Variant) -> GdUnitResult:
|
|||
# test is given clazz already an instance
|
||||
if is_instance(clazz):
|
||||
return GdUnitResult.success(clazz)
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return GdUnitResult.success(clazz.new())
|
||||
TYPE_STRING:
|
||||
if ClassDB.class_exists(clazz):
|
||||
if Engine.has_singleton(clazz):
|
||||
return GdUnitResult.error("Not allowed to create a instance for singelton '%s'." % clazz)
|
||||
if not ClassDB.can_instantiate(clazz):
|
||||
return GdUnitResult.error("Can't instance Engine class '%s'." % clazz)
|
||||
return GdUnitResult.success(ClassDB.instantiate(clazz))
|
||||
var clazz_name: String = clazz
|
||||
if ClassDB.class_exists(clazz_name):
|
||||
if Engine.has_singleton(clazz_name):
|
||||
return GdUnitResult.error("Not allowed to create a instance for singelton '%s'." % clazz_name)
|
||||
if not ClassDB.can_instantiate(clazz_name):
|
||||
return GdUnitResult.error("Can't instance Engine class '%s'." % clazz_name)
|
||||
return GdUnitResult.success(ClassDB.instantiate(clazz_name))
|
||||
else:
|
||||
var clazz_path :String = extract_class_path(clazz)[0]
|
||||
var clazz_path :String = extract_class_path(clazz_name)[0]
|
||||
if not FileAccess.file_exists(clazz_path):
|
||||
return GdUnitResult.error("Class '%s' not found." % clazz)
|
||||
var script := load(clazz_path)
|
||||
return GdUnitResult.error("Class '%s' not found." % clazz_name)
|
||||
var script: GDScript = load(clazz_path)
|
||||
if script != null:
|
||||
return GdUnitResult.success(script.new())
|
||||
else:
|
||||
return GdUnitResult.error("Can't create instance for '%s'." % clazz)
|
||||
return GdUnitResult.error("Can't create instance for class '%s'." % clazz)
|
||||
return GdUnitResult.error("Can't create instance for '%s'." % clazz_name)
|
||||
return GdUnitResult.error("Can't create instance for class '%s'." % str(clazz))
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
static func extract_class_path(clazz :Variant) -> PackedStringArray:
|
||||
var clazz_path := PackedStringArray()
|
||||
if clazz is String:
|
||||
clazz_path.append(clazz)
|
||||
@warning_ignore("unsafe_cast")
|
||||
clazz_path.append(clazz as String)
|
||||
return clazz_path
|
||||
if is_instance(clazz):
|
||||
# is instance a script instance?
|
||||
var script := clazz.script as GDScript
|
||||
var script: GDScript = clazz.script
|
||||
if script != null:
|
||||
return extract_class_path(script)
|
||||
return clazz_path
|
||||
|
||||
if clazz is GDScript:
|
||||
if not clazz.resource_path.is_empty():
|
||||
clazz_path.append(clazz.resource_path)
|
||||
var script: GDScript = clazz
|
||||
if not script.resource_path.is_empty():
|
||||
clazz_path.append(script.resource_path)
|
||||
return clazz_path
|
||||
# if not found we go the expensive way and extract the path form the script by creating an instance
|
||||
var arg_list := build_function_default_arguments(clazz, "_init")
|
||||
var instance :Variant = clazz.callv("new", arg_list)
|
||||
var arg_list := build_function_default_arguments(script, "_init")
|
||||
var instance: Object = script.callv("new", arg_list)
|
||||
var clazz_info := inst_to_dict(instance)
|
||||
GdUnitTools.free_instance(instance)
|
||||
clazz_path.append(clazz_info["@path"])
|
||||
@warning_ignore("unsafe_cast")
|
||||
clazz_path.append(clazz_info["@path"] as String)
|
||||
if clazz_info.has("@subpath"):
|
||||
var sub_path :String = clazz_info["@subpath"]
|
||||
if not sub_path.is_empty():
|
||||
|
@ -529,33 +564,38 @@ static func extract_class_name(clazz :Variant) -> GdUnitResult:
|
|||
|
||||
if is_instance(clazz):
|
||||
# is instance a script instance?
|
||||
var script := clazz.script as GDScript
|
||||
var script: GDScript = clazz.script
|
||||
if script != null:
|
||||
return extract_class_name(script)
|
||||
return GdUnitResult.success(clazz.get_class())
|
||||
@warning_ignore("unsafe_cast")
|
||||
return GdUnitResult.success((clazz as Object).get_class())
|
||||
|
||||
# extract name form full qualified class path
|
||||
if clazz is String:
|
||||
if ClassDB.class_exists(clazz):
|
||||
return GdUnitResult.success(clazz)
|
||||
var source_sript :Script = load(clazz)
|
||||
var clazz_name :String = load("res://addons/gdUnit4/src/core/parse/GdScriptParser.gd").new().get_class_name(source_sript)
|
||||
var clazz_name: String = clazz
|
||||
if ClassDB.class_exists(clazz_name):
|
||||
return GdUnitResult.success(clazz_name)
|
||||
var source_script :GDScript = load(clazz_name)
|
||||
clazz_name = GdScriptParser.new().get_class_name(source_script)
|
||||
return GdUnitResult.success(to_pascal_case(clazz_name))
|
||||
|
||||
if is_primitive_type(clazz):
|
||||
return GdUnitResult.error("Can't extract class name for an primitive '%s'" % type_as_string(typeof(clazz)))
|
||||
|
||||
if is_script(clazz):
|
||||
if clazz.resource_path.is_empty():
|
||||
@warning_ignore("unsafe_cast")
|
||||
if (clazz as Script).resource_path.is_empty():
|
||||
var class_path := extract_class_name_from_class_path(extract_class_path(clazz))
|
||||
return GdUnitResult.success(class_path);
|
||||
return extract_class_name(clazz.resource_path)
|
||||
|
||||
# need to create an instance for a class typ the extract the class name
|
||||
@warning_ignore("unsafe_method_access")
|
||||
var instance :Variant = clazz.new()
|
||||
if instance == null:
|
||||
return GdUnitResult.error("Can't create a instance for class '%s'" % clazz)
|
||||
return GdUnitResult.error("Can't create a instance for class '%s'" % str(clazz))
|
||||
var result := extract_class_name(instance)
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitTools.free_instance(instance)
|
||||
return result
|
||||
|
||||
|
@ -571,6 +611,7 @@ static func extract_inner_clazz_names(clazz_name :String, script_path :PackedStr
|
|||
var value :Variant = map.get(key)
|
||||
if value is GDScript:
|
||||
var class_path := extract_class_path(value)
|
||||
@warning_ignore("return_value_discarded")
|
||||
inner_classes.append(class_path[1])
|
||||
return inner_classes
|
||||
|
||||
|
@ -649,6 +690,7 @@ static func default_value_by_type(type :int) -> Variant:
|
|||
TYPE_NODE_PATH: return NodePath()
|
||||
TYPE_RID: return RID()
|
||||
TYPE_OBJECT: return null
|
||||
TYPE_CALLABLE: return Callable()
|
||||
TYPE_ARRAY: return []
|
||||
TYPE_DICTIONARY: return {}
|
||||
TYPE_PACKED_BYTE_ARRAY: return PackedByteArray()
|
||||
|
|
|
@ -16,6 +16,7 @@ func _init(major :int, minor :int, patch :int) -> void:
|
|||
|
||||
static func parse(value :String) -> GdUnit4Version:
|
||||
var regex := RegEx.new()
|
||||
@warning_ignore("return_value_discarded")
|
||||
regex.compile("[a-zA-Z:,-]+")
|
||||
var cleaned := regex.sub(value, "", true)
|
||||
var parts := cleaned.split(".")
|
||||
|
@ -27,8 +28,10 @@ static func parse(value :String) -> GdUnit4Version:
|
|||
|
||||
static func current() -> GdUnit4Version:
|
||||
var config := ConfigFile.new()
|
||||
@warning_ignore("return_value_discarded")
|
||||
config.load('addons/gdUnit4/plugin.cfg')
|
||||
return parse(config.get_value('plugin', 'version'))
|
||||
@warning_ignore("unsafe_cast")
|
||||
return parse(config.get_value('plugin', 'version') as String)
|
||||
|
||||
|
||||
func equals(other :GdUnit4Version) -> bool:
|
||||
|
@ -45,12 +48,13 @@ func is_greater(other :GdUnit4Version) -> bool:
|
|||
|
||||
static func init_version_label(label :Control) -> void:
|
||||
var config := ConfigFile.new()
|
||||
@warning_ignore("return_value_discarded")
|
||||
config.load('addons/gdUnit4/plugin.cfg')
|
||||
var version :String = config.get_value('plugin', 'version')
|
||||
if label is RichTextLabel:
|
||||
label.text = VERSION_PATTERN.replace('${version}', version)
|
||||
(label as RichTextLabel).text = VERSION_PATTERN.replace('${version}', version)
|
||||
else:
|
||||
label.text = "gdUnit4 " + version
|
||||
(label as Label).text = "gdUnit4 " + version
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
|
|
|
@ -37,12 +37,15 @@ static func check_leaked_instances() -> void:
|
|||
# class_info = { "class_name": <>, "class_path" : <>}
|
||||
static func load_template(template :String, class_info :Dictionary, instance :Object) -> PackedStringArray:
|
||||
# store instance id
|
||||
var clazz_name: String = class_info.get("class_name")
|
||||
var source_code := template\
|
||||
.replace("${instance_id}", "%s%d" % [DOUBLER_INSTANCE_ID_PREFIX, abs(instance.get_instance_id())])\
|
||||
.replace("${source_class}", class_info.get("class_name"))
|
||||
.replace("${source_class}", clazz_name)
|
||||
var lines := GdScriptParser.to_unix_format(source_code).split("\n")
|
||||
# replace template class_name with Doubled<class> name and extends form source class
|
||||
lines.insert(0, "class_name Doubled%s" % class_info.get("class_name").replace(".", "_"))
|
||||
@warning_ignore("return_value_discarded")
|
||||
lines.insert(0, "class_name Doubled%s" % clazz_name.replace(".", "_"))
|
||||
@warning_ignore("return_value_discarded")
|
||||
lines.insert(1, extends_clazz(class_info))
|
||||
# append Object interactions stuff
|
||||
lines.append_array(GdScriptParser.to_unix_format(DOUBLER_TEMPLATE.source_code).split("\n"))
|
||||
|
@ -74,16 +77,14 @@ static func double_functions(instance :Object, clazz_name :String, clazz_path :P
|
|||
push_error(result.error_message())
|
||||
return PackedStringArray()
|
||||
var class_descriptor :GdClassDescriptor = result.value()
|
||||
while class_descriptor != null:
|
||||
for func_descriptor in class_descriptor.functions():
|
||||
if instance != null and not instance.has_method(func_descriptor.name()):
|
||||
#prints("no virtual func implemented",clazz_name, func_descriptor.name() )
|
||||
continue
|
||||
if functions.has(func_descriptor.name()) or exclude_override_functions.has(func_descriptor.name()):
|
||||
continue
|
||||
doubled_source += func_doubler.double(func_descriptor)
|
||||
functions.append(func_descriptor.name())
|
||||
class_descriptor = class_descriptor.parent()
|
||||
for func_descriptor in class_descriptor.functions():
|
||||
if instance != null and not instance.has_method(func_descriptor.name()):
|
||||
#prints("no virtual func implemented",clazz_name, func_descriptor.name() )
|
||||
continue
|
||||
if functions.has(func_descriptor.name()) or exclude_override_functions.has(func_descriptor.name()):
|
||||
continue
|
||||
doubled_source += func_doubler.double(func_descriptor, instance is CallableDoubler)
|
||||
functions.append(func_descriptor.name())
|
||||
|
||||
# double regular class functions
|
||||
var clazz_functions := GdObjects.extract_class_functions(clazz_name, clazz_path)
|
||||
|
@ -103,7 +104,7 @@ static func double_functions(instance :Object, clazz_name :String, clazz_path :P
|
|||
#prints("no virtual func implemented",clazz_name, func_descriptor.name() )
|
||||
continue
|
||||
functions.append(func_descriptor.name())
|
||||
doubled_source.append_array(func_doubler.double(func_descriptor))
|
||||
doubled_source.append_array(func_doubler.double(func_descriptor, instance is CallableDoubler))
|
||||
return doubled_source
|
||||
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ static func create_temp_file(relative_path :String, file_name :String, mode := F
|
|||
|
||||
static func temp_dir() -> String:
|
||||
if not DirAccess.dir_exists_absolute(GDUNIT_TEMP):
|
||||
@warning_ignore("return_value_discarded")
|
||||
DirAccess.make_dir_recursive_absolute(GDUNIT_TEMP)
|
||||
return GDUNIT_TEMP
|
||||
|
||||
|
@ -30,6 +31,7 @@ static func temp_dir() -> String:
|
|||
static func create_temp_dir(folder_name :String) -> String:
|
||||
var new_folder := temp_dir() + "/" + folder_name
|
||||
if not DirAccess.dir_exists_absolute(new_folder):
|
||||
@warning_ignore("return_value_discarded")
|
||||
DirAccess.make_dir_recursive_absolute(new_folder)
|
||||
return new_folder
|
||||
|
||||
|
@ -61,6 +63,7 @@ static func copy_directory(from_dir :String, to_dir :String, recursive :bool = f
|
|||
var source_dir := DirAccess.open(from_dir)
|
||||
var dest_dir := DirAccess.open(to_dir)
|
||||
if source_dir != null:
|
||||
@warning_ignore("return_value_discarded")
|
||||
source_dir.list_dir_begin()
|
||||
var next := "."
|
||||
|
||||
|
@ -72,6 +75,7 @@ static func copy_directory(from_dir :String, to_dir :String, recursive :bool = f
|
|||
var dest := dest_dir.get_current_dir() + "/" + next
|
||||
if source_dir.current_is_dir():
|
||||
if recursive:
|
||||
@warning_ignore("return_value_discarded")
|
||||
copy_directory(source + "/", dest, recursive)
|
||||
continue
|
||||
var err := source_dir.copy(source, dest)
|
||||
|
@ -88,6 +92,7 @@ static func copy_directory(from_dir :String, to_dir :String, recursive :bool = f
|
|||
static func delete_directory(path :String, only_content := false) -> void:
|
||||
var dir := DirAccess.open(path)
|
||||
if dir != null:
|
||||
@warning_ignore("return_value_discarded")
|
||||
dir.list_dir_begin()
|
||||
var file_name := "."
|
||||
while file_name != "":
|
||||
|
@ -113,6 +118,7 @@ static func delete_path_index_lower_equals_than(path :String, prefix :String, in
|
|||
if dir == null:
|
||||
return 0
|
||||
var deleted := 0
|
||||
@warning_ignore("return_value_discarded")
|
||||
dir.list_dir_begin()
|
||||
var next := "."
|
||||
while next != "":
|
||||
|
@ -134,6 +140,7 @@ static func find_last_path_index(path :String, prefix :String) -> int:
|
|||
if dir == null:
|
||||
return 0
|
||||
var last_iteration := 0
|
||||
@warning_ignore("return_value_discarded")
|
||||
dir.list_dir_begin()
|
||||
var next := "."
|
||||
while next != "":
|
||||
|
@ -152,12 +159,14 @@ static func scan_dir(path :String) -> PackedStringArray:
|
|||
if dir == null or not dir.dir_exists(path):
|
||||
return PackedStringArray()
|
||||
var content := PackedStringArray()
|
||||
@warning_ignore("return_value_discarded")
|
||||
dir.list_dir_begin()
|
||||
var next := "."
|
||||
while next != "":
|
||||
next = dir.get_next()
|
||||
if next.is_empty() or next == "." or next == "..":
|
||||
continue
|
||||
@warning_ignore("return_value_discarded")
|
||||
content.append(next)
|
||||
return content
|
||||
|
||||
|
@ -169,6 +178,7 @@ static func resource_as_array(resource_path :String) -> PackedStringArray:
|
|||
return PackedStringArray()
|
||||
var file_content := PackedStringArray()
|
||||
while not file.eof_reached():
|
||||
@warning_ignore("return_value_discarded")
|
||||
file_content.append(file.get_line())
|
||||
return file_content
|
||||
|
||||
|
@ -182,11 +192,12 @@ static func resource_as_string(resource_path :String) -> String:
|
|||
|
||||
|
||||
static func make_qualified_path(path :String) -> String:
|
||||
if not path.begins_with("res://"):
|
||||
if path.begins_with("//"):
|
||||
return path.replace("//", "res://")
|
||||
if path.begins_with("/"):
|
||||
return "res:/" + path
|
||||
if path.begins_with("res://"):
|
||||
return path
|
||||
if path.begins_with("//"):
|
||||
return path.replace("//", "res://")
|
||||
if path.begins_with("/"):
|
||||
return "res:/" + path
|
||||
return path
|
||||
|
||||
|
||||
|
@ -203,9 +214,11 @@ static func extract_zip(zip_package :String, dest_path :String) -> GdUnitResult:
|
|||
for zip_entry in zip_entries:
|
||||
var new_file_path: String = dest_path + "/" + zip_entry.replace(archive_path, "")
|
||||
if zip_entry.ends_with("/"):
|
||||
@warning_ignore("return_value_discarded")
|
||||
DirAccess.make_dir_recursive_absolute(new_file_path)
|
||||
continue
|
||||
var file: FileAccess = FileAccess.open(new_file_path, FileAccess.WRITE)
|
||||
file.store_buffer(zip.read_file(zip_entry))
|
||||
@warning_ignore("return_value_discarded")
|
||||
zip.close()
|
||||
return GdUnitResult.success(dest_path)
|
||||
|
|
|
@ -5,13 +5,15 @@ extends RefCounted
|
|||
static func verify(interaction_object :Object, interactions_times :int) -> Variant:
|
||||
if not _is_mock_or_spy(interaction_object, "__verify"):
|
||||
return interaction_object
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return interaction_object.__do_verify_interactions(interactions_times)
|
||||
|
||||
|
||||
static func verify_no_interactions(interaction_object :Object) -> GdUnitAssert:
|
||||
var __gd_assert :GdUnitAssert = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new("")
|
||||
var __gd_assert := GdUnitAssertImpl.new("")
|
||||
if not _is_mock_or_spy(interaction_object, "__verify"):
|
||||
return __gd_assert.report_success()
|
||||
@warning_ignore("unsafe_method_access")
|
||||
var __summary :Dictionary = interaction_object.__verify_no_interactions()
|
||||
if __summary.is_empty():
|
||||
return __gd_assert.report_success()
|
||||
|
@ -19,9 +21,10 @@ static func verify_no_interactions(interaction_object :Object) -> GdUnitAssert:
|
|||
|
||||
|
||||
static func verify_no_more_interactions(interaction_object :Object) -> GdUnitAssert:
|
||||
var __gd_assert :GdUnitAssert = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new("")
|
||||
var __gd_assert := GdUnitAssertImpl.new("")
|
||||
if not _is_mock_or_spy(interaction_object, "__verify_no_more_interactions"):
|
||||
return __gd_assert
|
||||
@warning_ignore("unsafe_method_access")
|
||||
var __summary :Dictionary = interaction_object.__verify_no_more_interactions()
|
||||
if __summary.is_empty():
|
||||
return __gd_assert
|
||||
|
@ -31,12 +34,14 @@ static func verify_no_more_interactions(interaction_object :Object) -> GdUnitAss
|
|||
static func reset(interaction_object :Object) -> Object:
|
||||
if not _is_mock_or_spy(interaction_object, "__reset"):
|
||||
return interaction_object
|
||||
@warning_ignore("unsafe_method_access")
|
||||
interaction_object.__reset_interactions()
|
||||
return interaction_object
|
||||
|
||||
|
||||
static func _is_mock_or_spy(interaction_object :Object, mock_function_signature :String) -> bool:
|
||||
if interaction_object is GDScript and not interaction_object.get_script().has_script_method(mock_function_signature):
|
||||
@warning_ignore("unsafe_cast")
|
||||
if interaction_object is GDScript and not (interaction_object.get_script() as GDScript).has_method(mock_function_signature):
|
||||
push_error("Error: You try to use a non mock or spy!")
|
||||
return false
|
||||
return true
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
const GdUnitAssertImpl := preload("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd")
|
||||
|
||||
var __expected_interactions :int = -1
|
||||
var __saved_interactions := Dictionary()
|
||||
|
@ -47,8 +46,10 @@ func __verify_interactions(function_args :Array[Variant]) -> void:
|
|||
__error_message = GdAssertMessages.error_validate_interactions(__current_summary, __expected_summary)
|
||||
else:
|
||||
__error_message = GdAssertMessages.error_validate_interactions(__summary, __expected_summary)
|
||||
@warning_ignore("return_value_discarded")
|
||||
__gd_assert.report_error(__error_message)
|
||||
else:
|
||||
@warning_ignore("return_value_discarded")
|
||||
__gd_assert.report_success()
|
||||
__expected_interactions = -1
|
||||
|
||||
|
|
|
@ -31,6 +31,10 @@ func value() -> Variant:
|
|||
return _value
|
||||
|
||||
|
||||
func value_as_string() -> String:
|
||||
return _value
|
||||
|
||||
|
||||
func value_set() -> PackedStringArray:
|
||||
return _value_set
|
||||
|
||||
|
@ -44,11 +48,11 @@ func set_value(p_value :Variant) -> void:
|
|||
TYPE_STRING:
|
||||
_value = str(p_value)
|
||||
TYPE_BOOL:
|
||||
_value = bool(p_value)
|
||||
_value = convert(p_value, TYPE_BOOL)
|
||||
TYPE_INT:
|
||||
_value = int(p_value)
|
||||
_value = convert(p_value, TYPE_INT)
|
||||
TYPE_FLOAT:
|
||||
_value = float(p_value)
|
||||
_value = convert(p_value, TYPE_FLOAT)
|
||||
_:
|
||||
_value = p_value
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ enum {
|
|||
EMPTY
|
||||
}
|
||||
|
||||
var _state :Variant
|
||||
var _state: int
|
||||
var _warn_message := ""
|
||||
var _error_message := ""
|
||||
var _value :Variant = null
|
||||
|
@ -66,6 +66,10 @@ func value() -> Variant:
|
|||
return _value
|
||||
|
||||
|
||||
func value_as_string() -> String:
|
||||
return _value
|
||||
|
||||
|
||||
func or_else(p_value :Variant) -> Variant:
|
||||
if not is_success():
|
||||
return p_value
|
||||
|
@ -97,7 +101,8 @@ static func serialize(result :GdUnitResult) -> Dictionary:
|
|||
|
||||
static func deserialize(config :Dictionary) -> GdUnitResult:
|
||||
var result := GdUnitResult.new()
|
||||
result._value = str_to_var(config.get("value", ""))
|
||||
var cfg_value: String = config.get("value", "")
|
||||
result._value = str_to_var(cfg_value)
|
||||
result._warn_message = config.get("warn_msg", null)
|
||||
result._error_message = config.get("err_msg", null)
|
||||
result._state = config.get("state")
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
extends Node
|
||||
|
||||
signal sync_rpc_id_result_received
|
||||
|
||||
|
||||
@onready var _client :GdUnitTcpClient = $GdUnitTcpClient
|
||||
@onready var _executor :GdUnitTestSuiteExecutor = GdUnitTestSuiteExecutor.new()
|
||||
|
||||
|
@ -39,7 +36,9 @@ func _ready() -> void:
|
|||
push_error(config_result.error_message())
|
||||
_state = EXIT
|
||||
return
|
||||
@warning_ignore("return_value_discarded")
|
||||
_client.connect("connection_failed", _on_connection_failed)
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event)
|
||||
var result := _client.start("127.0.0.1", _config.server_port())
|
||||
if result.is_error():
|
||||
|
@ -78,11 +77,14 @@ func _process(_delta :float) -> void:
|
|||
# process next test suite
|
||||
set_process(false)
|
||||
var test_suite :Node = _test_suites_to_process.pop_front()
|
||||
@warning_ignore("unsafe_method_access")
|
||||
if _cs_executor != null and _cs_executor.IsExecutable(test_suite):
|
||||
@warning_ignore("unsafe_method_access")
|
||||
_cs_executor.Execute(test_suite)
|
||||
@warning_ignore("unsafe_property_access")
|
||||
await _cs_executor.ExecutionCompleted
|
||||
else:
|
||||
await _executor.execute(test_suite)
|
||||
await _executor.execute(test_suite as GdUnitTestSuite)
|
||||
set_process(true)
|
||||
STOP:
|
||||
_state = EXIT
|
||||
|
@ -136,6 +138,7 @@ func _do_filter_test_case(test_suite :Node, test_case :Node, included_tests :Pac
|
|||
# we have a paremeterized test selection
|
||||
if test_meta.size() > 1:
|
||||
var test_param_index := test_meta[1]
|
||||
@warning_ignore("unsafe_method_access")
|
||||
test_case.set_test_parameter_index(test_param_index.to_int())
|
||||
return
|
||||
# the test is filtered out
|
||||
|
|
|
@ -38,6 +38,7 @@ func server_port() -> int:
|
|||
return _config.get(SERVER_PORT, -1)
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func self_test() -> GdUnitRunnerConfig:
|
||||
add_test_suite("res://addons/gdUnit4/test/")
|
||||
add_test_suite("res://addons/gdUnit4/mono/test/")
|
||||
|
@ -52,6 +53,7 @@ func add_test_suite(p_resource_path :String) -> GdUnitRunnerConfig:
|
|||
|
||||
func add_test_suites(resource_paths :PackedStringArray) -> GdUnitRunnerConfig:
|
||||
for resource_path_ in resource_paths:
|
||||
@warning_ignore("return_value_discarded")
|
||||
add_test_suite(resource_path_)
|
||||
return self
|
||||
|
||||
|
@ -60,8 +62,10 @@ func add_test_case(p_resource_path :String, test_name :StringName, test_param_in
|
|||
var to_execute_ := to_execute()
|
||||
var test_cases :PackedStringArray = to_execute_.get(p_resource_path, PackedStringArray())
|
||||
if test_param_index != -1:
|
||||
@warning_ignore("return_value_discarded")
|
||||
test_cases.append("%s:%d" % [test_name, test_param_index])
|
||||
else:
|
||||
@warning_ignore("return_value_discarded")
|
||||
test_cases.append(test_name)
|
||||
to_execute_[p_resource_path] = test_cases
|
||||
return self
|
||||
|
@ -72,18 +76,22 @@ func add_test_case(p_resource_path :String, test_name :StringName, test_param_in
|
|||
# '/path/path', res://path/path', 'res://path/path/testsuite.gd' or 'testsuite'
|
||||
# 'res://path/path/testsuite.gd:test_case' or 'testsuite:test_case'
|
||||
func skip_test_suite(value :StringName) -> GdUnitRunnerConfig:
|
||||
var parts :Array = GdUnitFileAccess.make_qualified_path(value).rsplit(":")
|
||||
var parts: PackedStringArray = GdUnitFileAccess.make_qualified_path(value).rsplit(":")
|
||||
if parts[0] == "res":
|
||||
parts.pop_front()
|
||||
parts.remove_at(0)
|
||||
parts[0] = GdUnitFileAccess.make_qualified_path(parts[0])
|
||||
match parts.size():
|
||||
1: skipped()[parts[0]] = PackedStringArray()
|
||||
2: skip_test_case(parts[0], parts[1])
|
||||
1:
|
||||
skipped()[parts[0]] = PackedStringArray()
|
||||
2:
|
||||
@warning_ignore("return_value_discarded")
|
||||
skip_test_case(parts[0], parts[1])
|
||||
return self
|
||||
|
||||
|
||||
func skip_test_suites(resource_paths :PackedStringArray) -> GdUnitRunnerConfig:
|
||||
for resource_path_ in resource_paths:
|
||||
@warning_ignore("return_value_discarded")
|
||||
skip_test_suite(resource_path_)
|
||||
return self
|
||||
|
||||
|
@ -91,6 +99,7 @@ func skip_test_suites(resource_paths :PackedStringArray) -> GdUnitRunnerConfig:
|
|||
func skip_test_case(p_resource_path :String, test_name :StringName) -> GdUnitRunnerConfig:
|
||||
var to_ignore := skipped()
|
||||
var test_cases :PackedStringArray = to_ignore.get(p_resource_path, PackedStringArray())
|
||||
@warning_ignore("return_value_discarded")
|
||||
test_cases.append(test_name)
|
||||
to_ignore[p_resource_path] = test_cases
|
||||
return self
|
||||
|
@ -129,19 +138,20 @@ func load_config(path :String = CONFIG_FILE) -> GdUnitResult:
|
|||
var error := test_json_conv.parse(content)
|
||||
if error != OK:
|
||||
return GdUnitResult.error("The runner configuration '%s' is invalid! The format is changed please delete it manually and start a new test run." % path)
|
||||
_config = test_json_conv.get_data() as Dictionary
|
||||
_config = test_json_conv.get_data()
|
||||
if not _config.has(VERSION):
|
||||
return GdUnitResult.error("The runner configuration '%s' is invalid! The format is changed please delete it manually and start a new test run." % path)
|
||||
fix_value_types()
|
||||
return GdUnitResult.success(path)
|
||||
|
||||
|
||||
@warning_ignore("unsafe_cast")
|
||||
func fix_value_types() -> void:
|
||||
# fix float value to int json stores all numbers as float
|
||||
var server_port_ :int = _config.get(SERVER_PORT, -1)
|
||||
_config[SERVER_PORT] = server_port_
|
||||
convert_Array_to_PackedStringArray(_config[INCLUDED])
|
||||
convert_Array_to_PackedStringArray(_config[SKIPPED])
|
||||
convert_Array_to_PackedStringArray(_config[INCLUDED] as Dictionary)
|
||||
convert_Array_to_PackedStringArray(_config[SKIPPED] as Dictionary)
|
||||
|
||||
|
||||
func convert_Array_to_PackedStringArray(data :Dictionary) -> void:
|
||||
|
|
|
@ -3,7 +3,7 @@ class_name GdUnitSceneRunnerImpl
|
|||
extends GdUnitSceneRunner
|
||||
|
||||
|
||||
var GdUnitFuncAssertImpl := ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE)
|
||||
var GdUnitFuncAssertImpl: GDScript = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE)
|
||||
|
||||
|
||||
# mapping of mouse buttons and his masks
|
||||
|
@ -19,29 +19,36 @@ const MAP_MOUSE_BUTTON_MASKS := {
|
|||
}
|
||||
|
||||
var _is_disposed := false
|
||||
var _current_scene :Node = null
|
||||
var _awaiter :GdUnitAwaiter = GdUnitAwaiter.new()
|
||||
var _verbose :bool
|
||||
var _simulate_start_time :LocalTime
|
||||
var _last_input_event :InputEvent = null
|
||||
var _current_scene: Node = null
|
||||
var _awaiter: GdUnitAwaiter = GdUnitAwaiter.new()
|
||||
var _verbose: bool
|
||||
var _simulate_start_time: LocalTime
|
||||
var _last_input_event: InputEvent = null
|
||||
var _mouse_button_on_press := []
|
||||
var _key_on_press := []
|
||||
var _action_on_press := []
|
||||
var _curent_mouse_position :Vector2
|
||||
var _curent_mouse_position: Vector2
|
||||
# holds the touch position for each touch index
|
||||
# { index: int = position: Vector2}
|
||||
var _current_touch_position: Dictionary = {}
|
||||
# holds the curretn touch drag position
|
||||
var _current_touch_drag_position: Vector2 = Vector2.ZERO
|
||||
|
||||
# time factor settings
|
||||
var _time_factor := 1.0
|
||||
var _saved_iterations_per_second :float
|
||||
var _saved_iterations_per_second: float
|
||||
var _scene_auto_free := false
|
||||
|
||||
|
||||
func _init(p_scene :Variant, p_verbose :bool, p_hide_push_errors := false) -> void:
|
||||
func _init(p_scene: Variant, p_verbose: bool, p_hide_push_errors := false) -> void:
|
||||
_verbose = p_verbose
|
||||
_saved_iterations_per_second = Engine.get_physics_ticks_per_second()
|
||||
@warning_ignore("return_value_discarded")
|
||||
set_time_factor(1)
|
||||
# handle scene loading by resource path
|
||||
if typeof(p_scene) == TYPE_STRING:
|
||||
if !ResourceLoader.exists(p_scene):
|
||||
@warning_ignore("unsafe_cast")
|
||||
if !ResourceLoader.exists(p_scene as String):
|
||||
if not p_hide_push_errors:
|
||||
push_error("GdUnitSceneRunner: Can't load scene by given resource path: '%s'. The resource does not exists." % p_scene)
|
||||
return
|
||||
|
@ -49,7 +56,8 @@ func _init(p_scene :Variant, p_verbose :bool, p_hide_push_errors := false) -> vo
|
|||
if not p_hide_push_errors:
|
||||
push_error("GdUnitSceneRunner: The given resource: '%s'. is not a scene." % p_scene)
|
||||
return
|
||||
_current_scene = load(p_scene).instantiate()
|
||||
@warning_ignore("unsafe_cast")
|
||||
_current_scene = (load(p_scene as String) as PackedScene).instantiate()
|
||||
_scene_auto_free = true
|
||||
else:
|
||||
# verify we have a node instance
|
||||
|
@ -62,10 +70,14 @@ func _init(p_scene :Variant, p_verbose :bool, p_hide_push_errors := false) -> vo
|
|||
if not p_hide_push_errors:
|
||||
push_error("GdUnitSceneRunner: Scene must be not null!")
|
||||
return
|
||||
|
||||
_scene_tree().root.add_child(_current_scene)
|
||||
# do finally reset all open input events when the scene is removed
|
||||
@warning_ignore("return_value_discarded")
|
||||
_scene_tree().root.child_exiting_tree.connect(func f(child :Node) -> void:
|
||||
if child == _current_scene:
|
||||
# we need to disable the processing to avoid input flush buffer errors
|
||||
_current_scene.process_mode = Node.PROCESS_MODE_DISABLED
|
||||
_reset_input_to_default()
|
||||
)
|
||||
_simulate_start_time = LocalTime.now()
|
||||
|
@ -78,7 +90,7 @@ func _init(p_scene :Variant, p_verbose :bool, p_hide_push_errors := false) -> vo
|
|||
max_iteration_to_wait += 1
|
||||
|
||||
|
||||
func _notification(what :int) -> void:
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE and is_instance_valid(self):
|
||||
# reset time factor to normal
|
||||
__deactivate_time_factor()
|
||||
|
@ -89,45 +101,52 @@ func _notification(what :int) -> void:
|
|||
_current_scene.free()
|
||||
_is_disposed = true
|
||||
_current_scene = null
|
||||
# we hide the scene/main window after runner is finished
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED)
|
||||
|
||||
|
||||
func _scene_tree() -> SceneTree:
|
||||
return Engine.get_main_loop() as SceneTree
|
||||
|
||||
|
||||
func simulate_action_pressed(action :String) -> GdUnitSceneRunner:
|
||||
@warning_ignore("return_value_discarded")
|
||||
func simulate_action_pressed(action: String) -> GdUnitSceneRunner:
|
||||
simulate_action_press(action)
|
||||
simulate_action_release(action)
|
||||
return self
|
||||
|
||||
|
||||
func simulate_action_press(action :String) -> GdUnitSceneRunner:
|
||||
func simulate_action_press(action: String) -> GdUnitSceneRunner:
|
||||
__print_current_focus()
|
||||
var event := InputEventAction.new()
|
||||
event.pressed = true
|
||||
event.action = action
|
||||
if Engine.get_version_info().hex >= 0x40300:
|
||||
@warning_ignore("unsafe_property_access")
|
||||
event.event_index = InputMap.get_actions().find(action)
|
||||
_action_on_press.append(action)
|
||||
return _handle_input_event(event)
|
||||
|
||||
|
||||
func simulate_action_release(action :String) -> GdUnitSceneRunner:
|
||||
func simulate_action_release(action: String) -> GdUnitSceneRunner:
|
||||
__print_current_focus()
|
||||
var event := InputEventAction.new()
|
||||
event.pressed = false
|
||||
event.action = action
|
||||
if Engine.get_version_info().hex >= 0x40300:
|
||||
@warning_ignore("unsafe_property_access")
|
||||
event.event_index = InputMap.get_actions().find(action)
|
||||
_action_on_press.erase(action)
|
||||
return _handle_input_event(event)
|
||||
|
||||
|
||||
func simulate_key_pressed(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
@warning_ignore("return_value_discarded")
|
||||
func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
simulate_key_press(key_code, shift_pressed, ctrl_pressed)
|
||||
await _scene_tree().process_frame
|
||||
simulate_key_release(key_code, shift_pressed, ctrl_pressed)
|
||||
return self
|
||||
|
||||
|
||||
func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
__print_current_focus()
|
||||
var event := InputEventKey.new()
|
||||
event.pressed = true
|
||||
|
@ -141,7 +160,7 @@ func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := f
|
|||
return _handle_input_event(event)
|
||||
|
||||
|
||||
func simulate_key_release(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
|
||||
__print_current_focus()
|
||||
var event := InputEventKey.new()
|
||||
event.pressed = false
|
||||
|
@ -155,7 +174,11 @@ func simulate_key_release(key_code :int, shift_pressed := false, ctrl_pressed :=
|
|||
return _handle_input_event(event)
|
||||
|
||||
|
||||
func set_mouse_pos(pos :Vector2) -> GdUnitSceneRunner:
|
||||
func set_mouse_pos(pos: Vector2) -> GdUnitSceneRunner:
|
||||
return set_mouse_position(pos)
|
||||
|
||||
|
||||
func set_mouse_position(pos: Vector2) -> GdUnitSceneRunner:
|
||||
var event := InputEventMouseMotion.new()
|
||||
event.position = pos
|
||||
event.global_position = get_global_mouse_position()
|
||||
|
@ -165,7 +188,7 @@ func set_mouse_pos(pos :Vector2) -> GdUnitSceneRunner:
|
|||
|
||||
func get_mouse_position() -> Vector2:
|
||||
if _last_input_event is InputEventMouse:
|
||||
return _last_input_event.position
|
||||
return (_last_input_event as InputEventMouse).position
|
||||
var current_scene := scene()
|
||||
if current_scene != null:
|
||||
return current_scene.get_viewport().get_mouse_position()
|
||||
|
@ -173,19 +196,20 @@ func get_mouse_position() -> Vector2:
|
|||
|
||||
|
||||
func get_global_mouse_position() -> Vector2:
|
||||
return Engine.get_main_loop().root.get_mouse_position()
|
||||
return (Engine.get_main_loop() as SceneTree).root.get_mouse_position()
|
||||
|
||||
|
||||
func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner:
|
||||
func simulate_mouse_move(position: Vector2) -> GdUnitSceneRunner:
|
||||
var event := InputEventMouseMotion.new()
|
||||
event.position = pos
|
||||
event.relative = pos - get_mouse_position()
|
||||
event.position = position
|
||||
event.relative = position - get_mouse_position()
|
||||
event.global_position = get_global_mouse_position()
|
||||
_apply_input_mouse_mask(event)
|
||||
_apply_input_modifiers(event)
|
||||
return _handle_input_event(event)
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
|
||||
var tween := _scene_tree().create_tween()
|
||||
_curent_mouse_position = get_mouse_position()
|
||||
|
@ -199,6 +223,7 @@ func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_ty
|
|||
return self
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
|
||||
var tween := _scene_tree().create_tween()
|
||||
_curent_mouse_position = get_mouse_position()
|
||||
|
@ -211,36 +236,166 @@ func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_ty
|
|||
return self
|
||||
|
||||
|
||||
func simulate_mouse_button_pressed(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner:
|
||||
simulate_mouse_button_press(buttonIndex, double_click)
|
||||
simulate_mouse_button_release(buttonIndex)
|
||||
@warning_ignore("return_value_discarded")
|
||||
func simulate_mouse_button_pressed(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner:
|
||||
simulate_mouse_button_press(button_index, double_click)
|
||||
simulate_mouse_button_release(button_index)
|
||||
return self
|
||||
|
||||
|
||||
func simulate_mouse_button_press(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner:
|
||||
func simulate_mouse_button_press(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner:
|
||||
var event := InputEventMouseButton.new()
|
||||
event.button_index = buttonIndex
|
||||
event.button_index = button_index
|
||||
event.pressed = true
|
||||
event.double_click = double_click
|
||||
_apply_input_mouse_position(event)
|
||||
_apply_input_mouse_mask(event)
|
||||
_apply_input_modifiers(event)
|
||||
_mouse_button_on_press.append(buttonIndex)
|
||||
_mouse_button_on_press.append(button_index)
|
||||
return _handle_input_event(event)
|
||||
|
||||
|
||||
func simulate_mouse_button_release(buttonIndex :MouseButton) -> GdUnitSceneRunner:
|
||||
func simulate_mouse_button_release(button_index: MouseButton) -> GdUnitSceneRunner:
|
||||
var event := InputEventMouseButton.new()
|
||||
event.button_index = buttonIndex
|
||||
event.button_index = button_index
|
||||
event.pressed = false
|
||||
_apply_input_mouse_position(event)
|
||||
_apply_input_mouse_mask(event)
|
||||
_apply_input_modifiers(event)
|
||||
_mouse_button_on_press.erase(buttonIndex)
|
||||
_mouse_button_on_press.erase(button_index)
|
||||
return _handle_input_event(event)
|
||||
|
||||
|
||||
func set_time_factor(time_factor := 1.0) -> GdUnitSceneRunner:
|
||||
@warning_ignore("return_value_discarded")
|
||||
func simulate_screen_touch_pressed(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner:
|
||||
simulate_screen_touch_press(index, position, double_tap)
|
||||
simulate_screen_touch_release(index)
|
||||
return self
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func simulate_screen_touch_press(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner:
|
||||
if is_emulate_mouse_from_touch():
|
||||
# we need to simulate in addition to the touch the mouse events
|
||||
set_mouse_pos(position)
|
||||
simulate_mouse_button_press(MOUSE_BUTTON_LEFT)
|
||||
# push touch press event at position
|
||||
var event := InputEventScreenTouch.new()
|
||||
event.window_id = scene().get_window().get_window_id()
|
||||
event.index = index
|
||||
event.position = position
|
||||
event.double_tap = double_tap
|
||||
event.pressed = true
|
||||
_current_scene.get_viewport().push_input(event)
|
||||
# save current drag position by index
|
||||
_current_touch_position[index] = position
|
||||
return self
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func simulate_screen_touch_release(index: int, double_tap := false) -> GdUnitSceneRunner:
|
||||
if is_emulate_mouse_from_touch():
|
||||
# we need to simulate in addition to the touch the mouse events
|
||||
simulate_mouse_button_release(MOUSE_BUTTON_LEFT)
|
||||
# push touch release event at position
|
||||
var event := InputEventScreenTouch.new()
|
||||
event.window_id = scene().get_window().get_window_id()
|
||||
event.index = index
|
||||
event.position = get_screen_touch_drag_position(index)
|
||||
event.pressed = false
|
||||
event.double_tap = (_last_input_event as InputEventScreenTouch).double_tap if _last_input_event is InputEventScreenTouch else double_tap
|
||||
_current_scene.get_viewport().push_input(event)
|
||||
return self
|
||||
|
||||
|
||||
func simulate_screen_touch_drag_relative(index: int, relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
|
||||
var current_position: Vector2 = _current_touch_position[index]
|
||||
return await _do_touch_drag_at(index, current_position + relative, time, trans_type)
|
||||
|
||||
|
||||
func simulate_screen_touch_drag_absolute(index: int, position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
|
||||
return await _do_touch_drag_at(index, position, time, trans_type)
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func simulate_screen_touch_drag_drop(index: int, position: Vector2, drop_position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
|
||||
simulate_screen_touch_press(index, position)
|
||||
return await _do_touch_drag_at(index, drop_position, time, trans_type)
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func simulate_screen_touch_drag(index: int, position: Vector2) -> GdUnitSceneRunner:
|
||||
if is_emulate_mouse_from_touch():
|
||||
simulate_mouse_move(position)
|
||||
var event := InputEventScreenDrag.new()
|
||||
event.window_id = scene().get_window().get_window_id()
|
||||
event.index = index
|
||||
event.position = position
|
||||
event.relative = _get_screen_touch_drag_position_or_default(index, position) - position
|
||||
event.velocity = event.relative / _scene_tree().root.get_process_delta_time()
|
||||
event.pressure = 1.0
|
||||
_current_touch_position[index] = position
|
||||
_current_scene.get_viewport().push_input(event)
|
||||
return self
|
||||
|
||||
|
||||
func get_screen_touch_drag_position(index: int) -> Vector2:
|
||||
if _current_touch_position.has(index):
|
||||
return _current_touch_position[index]
|
||||
push_error("No touch drag position for index '%d' is set!" % index)
|
||||
return Vector2.ZERO
|
||||
|
||||
|
||||
func is_emulate_mouse_from_touch() -> bool:
|
||||
return ProjectSettings.get_setting("input_devices/pointing/emulate_mouse_from_touch", true)
|
||||
|
||||
|
||||
func _get_screen_touch_drag_position_or_default(index: int, default_position: Vector2) -> Vector2:
|
||||
if _current_touch_position.has(index):
|
||||
return _current_touch_position[index]
|
||||
return default_position
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func _do_touch_drag_at(index: int, drag_position: Vector2, time: float, trans_type: Tween.TransitionType) -> GdUnitSceneRunner:
|
||||
# start draging
|
||||
var event := InputEventScreenDrag.new()
|
||||
event.window_id = scene().get_window().get_window_id()
|
||||
event.index = index
|
||||
event.position = get_screen_touch_drag_position(index)
|
||||
event.pressure = 1.0
|
||||
_current_touch_drag_position = event.position
|
||||
|
||||
var tween := _scene_tree().create_tween()
|
||||
tween.tween_property(self, "_current_touch_drag_position", drag_position, time).set_trans(trans_type)
|
||||
tween.play()
|
||||
|
||||
while not _current_touch_drag_position.is_equal_approx(drag_position):
|
||||
if is_emulate_mouse_from_touch():
|
||||
# we need to simulate in addition to the drag the mouse move events
|
||||
simulate_mouse_move(event.position)
|
||||
# send touche drag event to new position
|
||||
event.relative = _current_touch_drag_position - event.position
|
||||
event.velocity = event.relative / _scene_tree().root.get_process_delta_time()
|
||||
event.position = _current_touch_drag_position
|
||||
_current_scene.get_viewport().push_input(event)
|
||||
await _scene_tree().process_frame
|
||||
|
||||
# finaly drop it
|
||||
if is_emulate_mouse_from_touch():
|
||||
simulate_mouse_move(drag_position)
|
||||
simulate_mouse_button_release(MOUSE_BUTTON_LEFT)
|
||||
var touch_drop_event := InputEventScreenTouch.new()
|
||||
touch_drop_event.window_id = event.window_id
|
||||
touch_drop_event.index = event.index
|
||||
touch_drop_event.position = drag_position
|
||||
touch_drop_event.pressed = false
|
||||
_current_scene.get_viewport().push_input(touch_drop_event)
|
||||
await _scene_tree().process_frame
|
||||
return self
|
||||
|
||||
|
||||
func set_time_factor(time_factor: float = 1.0) -> GdUnitSceneRunner:
|
||||
_time_factor = min(9.0, time_factor)
|
||||
__activate_time_factor()
|
||||
__print("set time factor: %f" % _time_factor)
|
||||
|
@ -248,7 +403,7 @@ func set_time_factor(time_factor := 1.0) -> GdUnitSceneRunner:
|
|||
return self
|
||||
|
||||
|
||||
func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner:
|
||||
func simulate_frames(frames: int, delta_milli: int = -1) -> GdUnitSceneRunner:
|
||||
var time_shift_frames :int = max(1, frames / _time_factor)
|
||||
for frame in time_shift_frames:
|
||||
if delta_milli == -1:
|
||||
|
@ -259,74 +414,73 @@ func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner:
|
|||
|
||||
|
||||
func simulate_until_signal(
|
||||
signal_name :String,
|
||||
arg0 :Variant = NO_ARG,
|
||||
arg1 :Variant = NO_ARG,
|
||||
arg2 :Variant = NO_ARG,
|
||||
arg3 :Variant = NO_ARG,
|
||||
arg4 :Variant = NO_ARG,
|
||||
arg5 :Variant = NO_ARG,
|
||||
arg6 :Variant = NO_ARG,
|
||||
arg7 :Variant = NO_ARG,
|
||||
arg8 :Variant = NO_ARG,
|
||||
arg9 :Variant = NO_ARG) -> GdUnitSceneRunner:
|
||||
var args :Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
|
||||
signal_name: String,
|
||||
arg0: Variant = NO_ARG,
|
||||
arg1: Variant = NO_ARG,
|
||||
arg2: Variant = NO_ARG,
|
||||
arg3: Variant = NO_ARG,
|
||||
arg4: Variant = NO_ARG,
|
||||
arg5: Variant = NO_ARG,
|
||||
arg6: Variant = NO_ARG,
|
||||
arg7: Variant = NO_ARG,
|
||||
arg8: Variant = NO_ARG,
|
||||
arg9: Variant = NO_ARG) -> GdUnitSceneRunner:
|
||||
var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
|
||||
await _awaiter.await_signal_idle_frames(scene(), signal_name, args, 10000)
|
||||
return self
|
||||
|
||||
|
||||
func simulate_until_object_signal(
|
||||
source :Object,
|
||||
signal_name :String,
|
||||
arg0 :Variant = NO_ARG,
|
||||
arg1 :Variant = NO_ARG,
|
||||
arg2 :Variant = NO_ARG,
|
||||
arg3 :Variant = NO_ARG,
|
||||
arg4 :Variant = NO_ARG,
|
||||
arg5 :Variant = NO_ARG,
|
||||
arg6 :Variant = NO_ARG,
|
||||
arg7 :Variant = NO_ARG,
|
||||
arg8 :Variant = NO_ARG,
|
||||
arg9 :Variant = NO_ARG) -> GdUnitSceneRunner:
|
||||
var args :Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
|
||||
source: Object,
|
||||
signal_name: String,
|
||||
arg0: Variant = NO_ARG,
|
||||
arg1: Variant = NO_ARG,
|
||||
arg2: Variant = NO_ARG,
|
||||
arg3: Variant = NO_ARG,
|
||||
arg4: Variant = NO_ARG,
|
||||
arg5: Variant = NO_ARG,
|
||||
arg6: Variant = NO_ARG,
|
||||
arg7: Variant = NO_ARG,
|
||||
arg8: Variant = NO_ARG,
|
||||
arg9: Variant = NO_ARG) -> GdUnitSceneRunner:
|
||||
var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
|
||||
await _awaiter.await_signal_idle_frames(source, signal_name, args, 10000)
|
||||
return self
|
||||
|
||||
|
||||
func await_func(func_name :String, args := []) -> GdUnitFuncAssert:
|
||||
func await_func(func_name: String, args := []) -> GdUnitFuncAssert:
|
||||
return GdUnitFuncAssertImpl.new(scene(), func_name, args)
|
||||
|
||||
|
||||
func await_func_on(instance :Object, func_name :String, args := []) -> GdUnitFuncAssert:
|
||||
func await_func_on(instance: Object, func_name: String, args := []) -> GdUnitFuncAssert:
|
||||
return GdUnitFuncAssertImpl.new(instance, func_name, args)
|
||||
|
||||
|
||||
func await_signal(signal_name :String, args := [], timeout := 2000 ) -> void:
|
||||
func await_signal(signal_name: String, args := [], timeout := 2000 ) -> void:
|
||||
await _awaiter.await_signal_on(scene(), signal_name, args, timeout)
|
||||
|
||||
|
||||
func await_signal_on(source :Object, signal_name :String, args := [], timeout := 2000 ) -> void:
|
||||
func await_signal_on(source: Object, signal_name: String, args := [], timeout := 2000 ) -> void:
|
||||
await _awaiter.await_signal_on(source, signal_name, args, timeout)
|
||||
|
||||
|
||||
# maximizes the window to bring the scene visible
|
||||
func maximize_view() -> GdUnitSceneRunner:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
DisplayServer.window_move_to_foreground()
|
||||
return self
|
||||
|
||||
|
||||
func _property_exists(name :String) -> bool:
|
||||
func _property_exists(name: String) -> bool:
|
||||
return scene().get_property_list().any(func(properties :Dictionary) -> bool: return properties["name"] == name)
|
||||
|
||||
|
||||
func get_property(name :String) -> Variant:
|
||||
func get_property(name: String) -> Variant:
|
||||
if not _property_exists(name):
|
||||
return "The property '%s' not exist checked loaded scene." % name
|
||||
return scene().get(name)
|
||||
|
||||
|
||||
func set_property(name :String, value :Variant) -> bool:
|
||||
func set_property(name: String, value: Variant) -> bool:
|
||||
if not _property_exists(name):
|
||||
push_error("The property named '%s' cannot be set, it does not exist!" % name)
|
||||
return false;
|
||||
|
@ -335,24 +489,24 @@ func set_property(name :String, value :Variant) -> bool:
|
|||
|
||||
|
||||
func invoke(
|
||||
name :String,
|
||||
arg0 :Variant = NO_ARG,
|
||||
arg1 :Variant = NO_ARG,
|
||||
arg2 :Variant = NO_ARG,
|
||||
arg3 :Variant = NO_ARG,
|
||||
arg4 :Variant = NO_ARG,
|
||||
arg5 :Variant = NO_ARG,
|
||||
arg6 :Variant = NO_ARG,
|
||||
arg7 :Variant = NO_ARG,
|
||||
arg8 :Variant = NO_ARG,
|
||||
arg9 :Variant = NO_ARG) -> Variant:
|
||||
var args :Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
|
||||
name: String,
|
||||
arg0: Variant = NO_ARG,
|
||||
arg1: Variant = NO_ARG,
|
||||
arg2: Variant = NO_ARG,
|
||||
arg3: Variant = NO_ARG,
|
||||
arg4: Variant = NO_ARG,
|
||||
arg5: Variant = NO_ARG,
|
||||
arg6: Variant = NO_ARG,
|
||||
arg7: Variant = NO_ARG,
|
||||
arg8: Variant = NO_ARG,
|
||||
arg9: Variant = NO_ARG) -> Variant:
|
||||
var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
|
||||
if scene().has_method(name):
|
||||
return scene().callv(name, args)
|
||||
return "The method '%s' not exist checked loaded scene." % name
|
||||
|
||||
|
||||
func find_child(name :String, recursive :bool = true, owned :bool = false) -> Node:
|
||||
func find_child(name: String, recursive: bool = true, owned: bool = false) -> Node:
|
||||
return scene().find_child(name, recursive, owned)
|
||||
|
||||
|
||||
|
@ -377,37 +531,40 @@ func __deactivate_time_factor() -> void:
|
|||
|
||||
|
||||
# copy over current active modifiers
|
||||
func _apply_input_modifiers(event :InputEvent) -> void:
|
||||
func _apply_input_modifiers(event: InputEvent) -> void:
|
||||
if _last_input_event is InputEventWithModifiers and event is InputEventWithModifiers:
|
||||
event.meta_pressed = event.meta_pressed or _last_input_event.meta_pressed
|
||||
event.alt_pressed = event.alt_pressed or _last_input_event.alt_pressed
|
||||
event.shift_pressed = event.shift_pressed or _last_input_event.shift_pressed
|
||||
event.ctrl_pressed = event.ctrl_pressed or _last_input_event.ctrl_pressed
|
||||
var last_input_event := _last_input_event as InputEventWithModifiers
|
||||
var _event := event as InputEventWithModifiers
|
||||
_event.meta_pressed = _event.meta_pressed or last_input_event.meta_pressed
|
||||
_event.alt_pressed = _event.alt_pressed or last_input_event.alt_pressed
|
||||
_event.shift_pressed = _event.shift_pressed or last_input_event.shift_pressed
|
||||
_event.ctrl_pressed = _event.ctrl_pressed or last_input_event.ctrl_pressed
|
||||
# this line results into reset the control_pressed state!!!
|
||||
#event.command_or_control_autoremap = event.command_or_control_autoremap or _last_input_event.command_or_control_autoremap
|
||||
|
||||
|
||||
# copy over current active mouse mask and combine with curren mask
|
||||
func _apply_input_mouse_mask(event :InputEvent) -> void:
|
||||
func _apply_input_mouse_mask(event: InputEvent) -> void:
|
||||
# first apply last mask
|
||||
if _last_input_event is InputEventMouse and event is InputEventMouse:
|
||||
event.button_mask |= _last_input_event.button_mask
|
||||
(event as InputEventMouse).button_mask |= (_last_input_event as InputEventMouse).button_mask
|
||||
if event is InputEventMouseButton:
|
||||
var button_mask :int = MAP_MOUSE_BUTTON_MASKS.get(event.get_button_index(), 0)
|
||||
if event.is_pressed():
|
||||
event.button_mask |= button_mask
|
||||
var _event := event as InputEventMouseButton
|
||||
var button_mask :int = MAP_MOUSE_BUTTON_MASKS.get(_event.get_button_index(), 0)
|
||||
if _event.is_pressed():
|
||||
_event.button_mask |= button_mask
|
||||
else:
|
||||
event.button_mask ^= button_mask
|
||||
_event.button_mask ^= button_mask
|
||||
|
||||
|
||||
# copy over last mouse position if need
|
||||
func _apply_input_mouse_position(event :InputEvent) -> void:
|
||||
func _apply_input_mouse_position(event: InputEvent) -> void:
|
||||
if _last_input_event is InputEventMouse and event is InputEventMouseButton:
|
||||
event.position = _last_input_event.position
|
||||
(event as InputEventMouseButton).position = (_last_input_event as InputEventMouse).position
|
||||
|
||||
|
||||
## handle input action via Input modifieres
|
||||
func _handle_actions(event :InputEventAction) -> bool:
|
||||
func _handle_actions(event: InputEventAction) -> bool:
|
||||
if not InputMap.event_is_action(event, event.action, true):
|
||||
return false
|
||||
__print(" process action %s (%s) <- %s" % [scene(), _scene_name(), event.as_text()])
|
||||
|
@ -419,20 +576,23 @@ func _handle_actions(event :InputEventAction) -> bool:
|
|||
|
||||
|
||||
# for handling read https://docs.godotengine.org/en/stable/tutorials/inputs/inputevent.html?highlight=inputevent#how-does-it-work
|
||||
func _handle_input_event(event :InputEvent) -> GdUnitSceneRunner:
|
||||
@warning_ignore("return_value_discarded")
|
||||
func _handle_input_event(event: InputEvent) -> GdUnitSceneRunner:
|
||||
if event is InputEventMouse:
|
||||
Input.warp_mouse(event.position)
|
||||
Input.warp_mouse((event as InputEventMouse).position as Vector2)
|
||||
Input.parse_input_event(event)
|
||||
|
||||
if event is InputEventAction:
|
||||
_handle_actions(event)
|
||||
_handle_actions(event as InputEventAction)
|
||||
|
||||
Input.flush_buffered_events()
|
||||
var current_scene := scene()
|
||||
if is_instance_valid(current_scene):
|
||||
# do not flush events if node processing disabled otherwise we run into errors at tree removed
|
||||
if _current_scene.process_mode != Node.PROCESS_MODE_DISABLED:
|
||||
Input.flush_buffered_events()
|
||||
__print(" process event %s (%s) <- %s" % [current_scene, _scene_name(), event.as_text()])
|
||||
if(current_scene.has_method("_gui_input")):
|
||||
current_scene._gui_input(event)
|
||||
(current_scene as Control)._gui_input(event)
|
||||
if(current_scene.has_method("_unhandled_input")):
|
||||
current_scene._unhandled_input(event)
|
||||
current_scene.get_viewport().set_input_as_handled()
|
||||
|
@ -442,6 +602,7 @@ func _handle_input_event(event :InputEvent) -> GdUnitSceneRunner:
|
|||
return self
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func _reset_input_to_default() -> void:
|
||||
# reset all mouse button to inital state if need
|
||||
for m_button :int in _mouse_button_on_press.duplicate():
|
||||
|
@ -459,11 +620,12 @@ func _reset_input_to_default() -> void:
|
|||
simulate_action_release(action)
|
||||
_action_on_press.clear()
|
||||
|
||||
Input.flush_buffered_events()
|
||||
if is_instance_valid(_current_scene) and _current_scene.process_mode != Node.PROCESS_MODE_DISABLED:
|
||||
Input.flush_buffered_events()
|
||||
_last_input_event = null
|
||||
|
||||
|
||||
func __print(message :String) -> void:
|
||||
func __print(message: String) -> void:
|
||||
if _verbose:
|
||||
prints(message)
|
||||
|
||||
|
|
|
@ -14,8 +14,10 @@ const SERVER_TIMEOUT = GROUP_COMMON + "/server_connection_timeout_minutes"
|
|||
const GROUP_TEST = COMMON_SETTINGS + "/test"
|
||||
const TEST_TIMEOUT = GROUP_TEST + "/test_timeout_seconds"
|
||||
const TEST_LOOKUP_FOLDER = GROUP_TEST + "/test_lookup_folder"
|
||||
const TEST_SITE_NAMING_CONVENTION = GROUP_TEST + "/test_suite_naming_convention"
|
||||
const TEST_SUITE_NAMING_CONVENTION = GROUP_TEST + "/test_suite_naming_convention"
|
||||
const TEST_DISCOVER_ENABLED = GROUP_TEST + "/test_discovery"
|
||||
const TEST_FLAKY_CHECK = GROUP_TEST + "/flaky_check_enable"
|
||||
const TEST_FLAKY_MAX_RETRIES = GROUP_TEST + "/flaky_max_retries"
|
||||
|
||||
|
||||
# Report Setiings
|
||||
|
@ -81,7 +83,7 @@ const DEFAULT_TEST_TIMEOUT :int = 60*5
|
|||
const DEFAULT_TEST_LOOKUP_FOLDER := "test"
|
||||
|
||||
# help texts
|
||||
const HELP_TEST_LOOKUP_FOLDER := "Sets the subfolder for the search/creation of test suites. (leave empty to use source folder)"
|
||||
const HELP_TEST_LOOKUP_FOLDER := "Subfolder where test suites are located (or empty to use source folder directly)"
|
||||
|
||||
enum NAMING_CONVENTIONS {
|
||||
AUTO_DETECT,
|
||||
|
@ -90,29 +92,36 @@ enum NAMING_CONVENTIONS {
|
|||
}
|
||||
|
||||
|
||||
const _VALUE_SET_SEPARATOR = "\f" # ASCII Form-feed character (AKA page break)
|
||||
|
||||
|
||||
static func setup() -> void:
|
||||
create_property_if_need(UPDATE_NOTIFICATION_ENABLED, true, "Enables/Disables the update notification checked startup.")
|
||||
create_property_if_need(SERVER_TIMEOUT, DEFAULT_SERVER_TIMEOUT, "Sets the server connection timeout in minutes.")
|
||||
create_property_if_need(TEST_TIMEOUT, DEFAULT_TEST_TIMEOUT, "Sets the test case runtime timeout in seconds.")
|
||||
create_property_if_need(UPDATE_NOTIFICATION_ENABLED, true, "Show notification if new gdUnit4 version is found")
|
||||
# test settings
|
||||
create_property_if_need(SERVER_TIMEOUT, DEFAULT_SERVER_TIMEOUT, "Server connection timeout in minutes")
|
||||
create_property_if_need(TEST_TIMEOUT, DEFAULT_TEST_TIMEOUT, "Test case runtime timeout in seconds")
|
||||
create_property_if_need(TEST_LOOKUP_FOLDER, DEFAULT_TEST_LOOKUP_FOLDER, HELP_TEST_LOOKUP_FOLDER)
|
||||
create_property_if_need(TEST_SITE_NAMING_CONVENTION, NAMING_CONVENTIONS.AUTO_DETECT, "Sets test-suite genrate script name convention.", NAMING_CONVENTIONS.keys())
|
||||
create_property_if_need(TEST_DISCOVER_ENABLED, false, "Enables/Disables the automatic detection of tests by finding tests in test lookup folders at runtime.")
|
||||
create_property_if_need(REPORT_PUSH_ERRORS, false, "Enables/Disables report of push_error() as failure!")
|
||||
create_property_if_need(REPORT_SCRIPT_ERRORS, true, "Enables/Disables report of script errors as failure!")
|
||||
create_property_if_need(REPORT_ORPHANS, true, "Enables/Disables orphan reporting.")
|
||||
create_property_if_need(REPORT_ASSERT_ERRORS, true, "Enables/Disables error reporting checked asserts.")
|
||||
create_property_if_need(REPORT_ASSERT_WARNINGS, true, "Enables/Disables warning reporting checked asserts")
|
||||
create_property_if_need(REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, true, "Enabled/disabled number values will be compared strictly by type. (real vs int)")
|
||||
create_property_if_need(TEST_SUITE_NAMING_CONVENTION, NAMING_CONVENTIONS.AUTO_DETECT, "Naming convention to use when generating testsuites", NAMING_CONVENTIONS.keys())
|
||||
create_property_if_need(TEST_DISCOVER_ENABLED, false, "Automatically detect new tests in test lookup folders at runtime")
|
||||
create_property_if_need(TEST_FLAKY_CHECK, false, "Rerun tests on failure and mark them as FLAKY")
|
||||
create_property_if_need(TEST_FLAKY_MAX_RETRIES, 3, "Sets the number of retries for rerunning a flaky test")
|
||||
# report settings
|
||||
create_property_if_need(REPORT_PUSH_ERRORS, false, "Report push_error() as failure")
|
||||
create_property_if_need(REPORT_SCRIPT_ERRORS, true, "Report script errors as failure")
|
||||
create_property_if_need(REPORT_ORPHANS, true, "Report orphaned nodes after tests finish")
|
||||
create_property_if_need(REPORT_ASSERT_ERRORS, true, "Report assertion failures as errors")
|
||||
create_property_if_need(REPORT_ASSERT_WARNINGS, true, "Report assertion failures as warnings")
|
||||
create_property_if_need(REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, true, "Compare number values strictly by type (real vs int)")
|
||||
# inspector
|
||||
create_property_if_need(INSPECTOR_NODE_COLLAPSE, true,
|
||||
"Enables/Disables that the testsuite node is closed after a successful test run.")
|
||||
"Close testsuite node after a successful test run.")
|
||||
create_property_if_need(INSPECTOR_TREE_VIEW_MODE, GdUnitInspectorTreeConstants.TREE_VIEW_MODE.TREE,
|
||||
"Sets the inspector panel presentation.", GdUnitInspectorTreeConstants.TREE_VIEW_MODE.keys())
|
||||
"Inspector panel presentation mode", GdUnitInspectorTreeConstants.TREE_VIEW_MODE.keys())
|
||||
create_property_if_need(INSPECTOR_TREE_SORT_MODE, GdUnitInspectorTreeConstants.SORT_MODE.UNSORTED,
|
||||
"Sets the inspector panel presentation.", GdUnitInspectorTreeConstants.SORT_MODE.keys())
|
||||
"Inspector panel sorting mode", GdUnitInspectorTreeConstants.SORT_MODE.keys())
|
||||
create_property_if_need(INSPECTOR_TOOLBAR_BUTTON_RUN_OVERALL, false,
|
||||
"Shows/Hides the 'Run overall Tests' button in the inspector toolbar.")
|
||||
create_property_if_need(TEMPLATE_TS_GD, GdUnitTestSuiteTemplate.default_GD_template(), "Defines the test suite template")
|
||||
"Show 'Run overall Tests' button in the inspector toolbar")
|
||||
create_property_if_need(TEMPLATE_TS_GD, GdUnitTestSuiteTemplate.default_GD_template(), "Test suite template to use")
|
||||
create_shortcut_properties_if_need()
|
||||
migrate_properties()
|
||||
|
||||
|
@ -129,17 +138,17 @@ static func migrate_properties() -> void:
|
|||
|
||||
static func create_shortcut_properties_if_need() -> void:
|
||||
# inspector
|
||||
create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS), "Rerun of the last tests performed.")
|
||||
create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG), "Rerun of the last tests performed (Debug).")
|
||||
create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_OVERALL, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL), "Runs all tests (Debug).")
|
||||
create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_STOP, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.STOP_TEST_RUN), "Stops the current test execution.")
|
||||
create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS), "Rerun the most recently executed tests")
|
||||
create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG), "Rerun the most recently executed tests (Debug mode)")
|
||||
create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_OVERALL, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL), "Runs all tests (Debug mode)")
|
||||
create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_STOP, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.STOP_TEST_RUN), "Stop the current test execution")
|
||||
# script editor
|
||||
create_property_if_need(SHORTCUT_EDITOR_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE), "Runs the currently selected test.")
|
||||
create_property_if_need(SHORTCUT_EDITOR_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG), "Runs the currently selected test (Debug).")
|
||||
create_property_if_need(SHORTCUT_EDITOR_CREATE_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.CREATE_TEST), "Creates a new test case for the currently selected function.")
|
||||
create_property_if_need(SHORTCUT_EDITOR_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE), "Run the currently selected test")
|
||||
create_property_if_need(SHORTCUT_EDITOR_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG), "Run the currently selected test (Debug mode).")
|
||||
create_property_if_need(SHORTCUT_EDITOR_CREATE_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.CREATE_TEST), "Create a new test case for the currently selected function")
|
||||
# filesystem
|
||||
create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Runs all test suites on the selected folder or file.")
|
||||
create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Runs all test suites on the selected folder or file (Debug).")
|
||||
create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Run all test suites in the selected folder or file")
|
||||
create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Run all test suites in the selected folder or file (Debug)")
|
||||
|
||||
|
||||
static func create_property_if_need(name :String, default :Variant, help :="", value_set := PackedStringArray()) -> void:
|
||||
|
@ -148,7 +157,7 @@ static func create_property_if_need(name :String, default :Variant, help :="", v
|
|||
ProjectSettings.set_setting(name, default)
|
||||
|
||||
ProjectSettings.set_initial_value(name, default)
|
||||
help += "" if value_set.is_empty() else " %s" % value_set
|
||||
help = help if value_set.is_empty() else "%s%s%s" % [help, _VALUE_SET_SEPARATOR, value_set]
|
||||
set_help(name, default, help)
|
||||
|
||||
|
||||
|
@ -175,6 +184,7 @@ static func is_update_notification_enabled() -> bool:
|
|||
|
||||
static func set_update_notification(enable :bool) -> void:
|
||||
ProjectSettings.set_setting(UPDATE_NOTIFICATION_ENABLED, enable)
|
||||
@warning_ignore("return_value_discarded")
|
||||
ProjectSettings.save()
|
||||
|
||||
|
||||
|
@ -185,6 +195,7 @@ static func get_log_path() -> String:
|
|||
static func set_log_path(path :String) -> void:
|
||||
ProjectSettings.set_setting(STDOUT_ENABLE_TO_FILE, true)
|
||||
ProjectSettings.set_setting(STDOUT_WITE_TO_FILE, path)
|
||||
@warning_ignore("return_value_discarded")
|
||||
ProjectSettings.save()
|
||||
|
||||
|
||||
|
@ -261,6 +272,14 @@ static func is_test_discover_enabled() -> bool:
|
|||
return get_setting(TEST_DISCOVER_ENABLED, false)
|
||||
|
||||
|
||||
static func is_test_flaky_check_enabled() -> bool:
|
||||
return get_setting(TEST_FLAKY_CHECK, false)
|
||||
|
||||
|
||||
static func get_flaky_max_retries() -> int:
|
||||
return get_setting(TEST_FLAKY_MAX_RETRIES, 3)
|
||||
|
||||
|
||||
static func set_test_discover_enabled(enable :bool) -> void:
|
||||
var property := get_property(TEST_DISCOVER_ENABLED)
|
||||
property.set_value(enable)
|
||||
|
@ -271,29 +290,34 @@ static func is_log_enabled() -> bool:
|
|||
return ProjectSettings.get_setting(STDOUT_ENABLE_TO_FILE)
|
||||
|
||||
|
||||
static func list_settings(category :String) -> Array[GdUnitProperty]:
|
||||
var settings :Array[GdUnitProperty] = []
|
||||
static func list_settings(category: String) -> Array[GdUnitProperty]:
|
||||
var settings: Array[GdUnitProperty] = []
|
||||
for property in ProjectSettings.get_property_list():
|
||||
var property_name :String = property["name"]
|
||||
if property_name.begins_with(category):
|
||||
var value :Variant = ProjectSettings.get_setting(property_name)
|
||||
var default :Variant = ProjectSettings.property_get_revert(property_name)
|
||||
var help :String = property["hint_string"]
|
||||
var value_set := extract_value_set_from_help(help)
|
||||
settings.append(GdUnitProperty.new(property_name, property["type"], value, default, help, value_set))
|
||||
settings.append(build_property(property_name, property))
|
||||
return settings
|
||||
|
||||
|
||||
static func extract_value_set_from_help(value :String) -> PackedStringArray:
|
||||
var split_value := value.split(_VALUE_SET_SEPARATOR)
|
||||
if not split_value.size() > 1:
|
||||
return PackedStringArray()
|
||||
|
||||
var regex := RegEx.new()
|
||||
@warning_ignore("return_value_discarded")
|
||||
regex.compile("\\[(.+)\\]")
|
||||
var matches := regex.search_all(value)
|
||||
var matches := regex.search_all(split_value[1])
|
||||
if matches.is_empty():
|
||||
return PackedStringArray()
|
||||
var values :String = matches[0].get_string(1)
|
||||
var values: String = matches[0].get_string(1)
|
||||
return values.replacen(" ", "").replacen("\"", "").split(",", false)
|
||||
|
||||
|
||||
static func extract_help_text(value :String) -> String:
|
||||
return value.split(_VALUE_SET_SEPARATOR)[0]
|
||||
|
||||
|
||||
static func update_property(property :GdUnitProperty) -> Variant:
|
||||
var current_value :Variant = ProjectSettings.get_setting(property.name())
|
||||
if current_value != property.value():
|
||||
|
@ -315,7 +339,7 @@ static func reset_property(property :GdUnitProperty) -> void:
|
|||
static func validate_property_value(property :GdUnitProperty) -> Variant:
|
||||
match property.name():
|
||||
TEST_LOOKUP_FOLDER:
|
||||
return validate_lookup_folder(property.value())
|
||||
return validate_lookup_folder(property.value_as_string())
|
||||
_: return null
|
||||
|
||||
|
||||
|
@ -349,14 +373,19 @@ static func get_property(name :String) -> GdUnitProperty:
|
|||
for property in ProjectSettings.get_property_list():
|
||||
var property_name :String = property["name"]
|
||||
if property_name == name:
|
||||
var value :Variant = ProjectSettings.get_setting(property_name)
|
||||
var default :Variant = ProjectSettings.property_get_revert(property_name)
|
||||
var help :String = property["hint_string"]
|
||||
var value_set := extract_value_set_from_help(help)
|
||||
return GdUnitProperty.new(property_name, property["type"], value, default, help, value_set)
|
||||
return build_property(name, property)
|
||||
return null
|
||||
|
||||
|
||||
static func build_property(property_name: String, property: Dictionary) -> GdUnitProperty:
|
||||
var value: Variant = ProjectSettings.get_setting(property_name)
|
||||
var value_type: int = property["type"]
|
||||
var default: Variant = ProjectSettings.property_get_revert(property_name)
|
||||
var help: String = property["hint_string"]
|
||||
var value_set := extract_value_set_from_help(help)
|
||||
return GdUnitProperty.new(property_name, value_type, value, default, extract_help_text(help), value_set)
|
||||
|
||||
|
||||
static func migrate_property(old_property :String, new_property :String, default_value :Variant, help :String, converter := Callable()) -> void:
|
||||
var property := get_property(old_property)
|
||||
if property == null:
|
||||
|
@ -371,8 +400,10 @@ static func migrate_property(old_property :String, new_property :String, default
|
|||
|
||||
|
||||
static func dump_to_tmp() -> void:
|
||||
@warning_ignore("return_value_discarded")
|
||||
ProjectSettings.save_custom("user://project_settings.godot")
|
||||
|
||||
|
||||
static func restore_dump_from_tmp() -> void:
|
||||
@warning_ignore("return_value_discarded")
|
||||
DirAccess.copy_absolute("user://project_settings.godot", "res://project.godot")
|
||||
|
|
|
@ -41,12 +41,15 @@ func elapsed_time() -> float:
|
|||
|
||||
func on_signal(source :Object, signal_name :String, expected_signal_args :Array) -> Variant:
|
||||
# register checked signal to wait for
|
||||
@warning_ignore("return_value_discarded")
|
||||
source.connect(signal_name, _on_signal_emmited)
|
||||
# install timeout timer
|
||||
var scene_tree := Engine.get_main_loop() as SceneTree
|
||||
var timer := Timer.new()
|
||||
Engine.get_main_loop().root.add_child(timer)
|
||||
scene_tree.root.add_child(timer)
|
||||
timer.add_to_group("GdUnitTimers")
|
||||
timer.set_one_shot(true)
|
||||
@warning_ignore("return_value_discarded")
|
||||
timer.timeout.connect(_do_interrupt, CONNECT_DEFERRED)
|
||||
timer.start(_timeout_millis * 0.001 * Engine.get_time_scale())
|
||||
|
||||
|
@ -61,12 +64,13 @@ func on_signal(source :Object, signal_name :String, expected_signal_args :Array)
|
|||
value = [value]
|
||||
if expected_signal_args.size() == 0 or GdObjects.equals(value, expected_signal_args):
|
||||
break
|
||||
await Engine.get_main_loop().process_frame
|
||||
await scene_tree.process_frame
|
||||
|
||||
source.disconnect(signal_name, _on_signal_emmited)
|
||||
_time_left = timer.time_left
|
||||
await Engine.get_main_loop().process_frame
|
||||
if value is Array and value.size() == 1:
|
||||
await scene_tree.process_frame
|
||||
@warning_ignore("unsafe_cast")
|
||||
if value is Array and (value as Array).size() == 1:
|
||||
return value[0]
|
||||
return value
|
||||
|
||||
|
|
|
@ -30,8 +30,8 @@ func register_emitter(emitter :Object) -> void:
|
|||
return
|
||||
_collected_signals[emitter] = Dictionary()
|
||||
# connect to 'tree_exiting' of the emitter to finally release all acquired resources/connections.
|
||||
if emitter is Node and !emitter.tree_exiting.is_connected(unregister_emitter):
|
||||
emitter.tree_exiting.connect(unregister_emitter.bind(emitter))
|
||||
if emitter is Node and !(emitter as Node).tree_exiting.is_connected(unregister_emitter):
|
||||
(emitter as Node).tree_exiting.connect(unregister_emitter.bind(emitter))
|
||||
# connect to all signals of the emitter we want to collect
|
||||
for signal_def in emitter.get_signal_list():
|
||||
var signal_name :String = signal_def["name"]
|
||||
|
@ -54,6 +54,7 @@ func unregister_emitter(emitter :Object) -> void:
|
|||
var signal_name :String = signal_def["name"]
|
||||
if emitter.is_connected(signal_name, _on_signal_emmited):
|
||||
emitter.disconnect(signal_name, _on_signal_emmited.bind(emitter, signal_name))
|
||||
@warning_ignore("return_value_discarded")
|
||||
_collected_signals.erase(emitter)
|
||||
|
||||
|
||||
|
@ -77,7 +78,8 @@ func _on_signal_emmited(
|
|||
var emitter :Object = signal_args.pop_back()
|
||||
#prints("_on_signal_emmited:", emitter, signal_name, signal_args)
|
||||
if is_signal_collecting(emitter, signal_name):
|
||||
_collected_signals[emitter][signal_name].append(signal_args)
|
||||
@warning_ignore("unsafe_cast")
|
||||
(_collected_signals[emitter][signal_name] as Array).append(signal_args)
|
||||
|
||||
|
||||
func reset_received_signals(emitter :Object, signal_name: String, signal_args :Array) -> void:
|
||||
|
@ -85,12 +87,14 @@ func reset_received_signals(emitter :Object, signal_name: String, signal_args :A
|
|||
if _collected_signals.has(emitter):
|
||||
var signals_by_emitter :Dictionary = _collected_signals[emitter]
|
||||
if signals_by_emitter.has(signal_name):
|
||||
_collected_signals[emitter][signal_name].erase(signal_args)
|
||||
@warning_ignore("unsafe_cast")
|
||||
(_collected_signals[emitter][signal_name] as Array).erase(signal_args)
|
||||
#_debug_signal_list("after claer");
|
||||
|
||||
|
||||
func is_signal_collecting(emitter :Object, signal_name :String) -> bool:
|
||||
return _collected_signals.has(emitter) and _collected_signals[emitter].has(signal_name)
|
||||
@warning_ignore("unsafe_cast")
|
||||
return _collected_signals.has(emitter) and (_collected_signals[emitter] as Dictionary).has(signal_name)
|
||||
|
||||
|
||||
func match(emitter :Object, signal_name :String, args :Array) -> bool:
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
class_name GdUnitSignals
|
||||
extends RefCounted
|
||||
|
||||
@warning_ignore("unused_signal")
|
||||
signal gdunit_client_connected(client_id :int)
|
||||
@warning_ignore("unused_signal")
|
||||
signal gdunit_client_disconnected(client_id :int)
|
||||
@warning_ignore("unused_signal")
|
||||
signal gdunit_client_terminated()
|
||||
|
||||
@warning_ignore("unused_signal")
|
||||
signal gdunit_event(event :GdUnitEvent)
|
||||
@warning_ignore("unused_signal")
|
||||
signal gdunit_event_debug(event :GdUnitEvent)
|
||||
@warning_ignore("unused_signal")
|
||||
signal gdunit_add_test_suite(test_suite :GdUnitTestSuiteDto)
|
||||
@warning_ignore("unused_signal")
|
||||
signal gdunit_message(message :String)
|
||||
signal gdunit_report(execution_context_id :int, report :GdUnitReport)
|
||||
@warning_ignore("unused_signal")
|
||||
signal gdunit_set_test_failed(is_failed :bool)
|
||||
|
||||
@warning_ignore("unused_signal")
|
||||
signal gdunit_settings_changed(property :GdUnitProperty)
|
||||
|
||||
const META_KEY := "GdUnitSignals"
|
||||
|
@ -29,8 +36,10 @@ static func dispose() -> void:
|
|||
var signals := instance()
|
||||
# cleanup connected signals
|
||||
for signal_ in signals.get_signal_list():
|
||||
for connection in signals.get_signal_connection_list(signal_["name"]):
|
||||
connection["signal"].disconnect(connection["callable"])
|
||||
@warning_ignore("unsafe_cast")
|
||||
for connection in signals.get_signal_connection_list(signal_["name"] as StringName):
|
||||
var _signal: Signal = connection["signal"]
|
||||
var _callable: Callable = connection["callable"]
|
||||
_signal.disconnect(_callable)
|
||||
signals = null
|
||||
Engine.remove_meta(META_KEY)
|
||||
while signals.get_reference_count() > 0:
|
||||
signals.unreference()
|
||||
|
|
|
@ -17,18 +17,20 @@ static func instance(name :String, clazz :Callable) -> Variant:
|
|||
return Engine.get_meta(name)
|
||||
var singleton :Variant = clazz.call()
|
||||
if is_instance_of(singleton, RefCounted):
|
||||
push_error("Invalid singleton implementation detected for '%s' is `%s`!" % [name, singleton.get_class()])
|
||||
@warning_ignore("unsafe_cast")
|
||||
push_error("Invalid singleton implementation detected for '%s' is `%s`!" % [name, (singleton as RefCounted).get_class()])
|
||||
return
|
||||
|
||||
Engine.set_meta(name, singleton)
|
||||
GdUnitTools.prints_verbose("Register singleton '%s:%s'" % [name, singleton])
|
||||
var singletons :PackedStringArray = Engine.get_meta(MEATA_KEY, PackedStringArray())
|
||||
@warning_ignore("return_value_discarded")
|
||||
singletons.append(name)
|
||||
Engine.set_meta(MEATA_KEY, singletons)
|
||||
return singleton
|
||||
|
||||
|
||||
static func unregister(p_singleton :String) -> void:
|
||||
static func unregister(p_singleton :String, use_call_deferred :bool = false) -> void:
|
||||
var singletons :PackedStringArray = Engine.get_meta(MEATA_KEY, PackedStringArray())
|
||||
if singletons.has(p_singleton):
|
||||
GdUnitTools.prints_verbose("\n Unregister singleton '%s'" % p_singleton);
|
||||
|
@ -36,18 +38,19 @@ static func unregister(p_singleton :String) -> void:
|
|||
singletons.remove_at(index)
|
||||
var instance_ :Object = Engine.get_meta(p_singleton)
|
||||
GdUnitTools.prints_verbose(" Free singleton instance '%s:%s'" % [p_singleton, instance_])
|
||||
GdUnitTools.free_instance(instance_)
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitTools.free_instance(instance_, use_call_deferred)
|
||||
Engine.remove_meta(p_singleton)
|
||||
GdUnitTools.prints_verbose(" Successfully freed '%s'" % p_singleton)
|
||||
Engine.set_meta(MEATA_KEY, singletons)
|
||||
|
||||
|
||||
static func dispose() -> void:
|
||||
static func dispose(use_call_deferred :bool = false) -> void:
|
||||
# use a copy because unregister is modify the singletons array
|
||||
var singletons := PackedStringArray(Engine.get_meta(MEATA_KEY, PackedStringArray()))
|
||||
var singletons :PackedStringArray = Engine.get_meta(MEATA_KEY, PackedStringArray())
|
||||
GdUnitTools.prints_verbose("----------------------------------------------------------------")
|
||||
GdUnitTools.prints_verbose("Cleanup singletons %s" % singletons)
|
||||
for singleton in singletons:
|
||||
unregister(singleton)
|
||||
for singleton in PackedStringArray(singletons):
|
||||
unregister(singleton, use_call_deferred)
|
||||
Engine.remove_meta(MEATA_KEY)
|
||||
GdUnitTools.prints_verbose("----------------------------------------------------------------")
|
||||
|
|
|
@ -5,7 +5,9 @@ extends RefCounted
|
|||
static func create(source :Script, line_number :int) -> GdUnitResult:
|
||||
var test_suite_path := GdUnitTestSuiteScanner.resolve_test_suite_path(source.resource_path, GdUnitSettings.test_root_folder())
|
||||
# we need to save and close the testsuite and source if is current opened before modify
|
||||
@warning_ignore("return_value_discarded")
|
||||
ScriptEditorControls.save_an_open_script(source.resource_path)
|
||||
@warning_ignore("return_value_discarded")
|
||||
ScriptEditorControls.save_an_open_script(test_suite_path, true)
|
||||
if GdObjects.is_cs_script(source):
|
||||
return GdUnit4CSharpApiLoader.create_test_suite(source.resource_path, line_number+1, test_suite_path)
|
||||
|
|
|
@ -30,8 +30,10 @@ func prescan_testsuite_classes() -> void:
|
|||
var base_class :String = script_meta["base"]
|
||||
var resource_path :String = script_meta["path"]
|
||||
if base_class == "GdUnitTestSuite":
|
||||
@warning_ignore("return_value_discarded")
|
||||
_included_resources.append(resource_path)
|
||||
elif ClassDB.class_exists(base_class):
|
||||
@warning_ignore("return_value_discarded")
|
||||
_excluded_resources.append(resource_path)
|
||||
|
||||
|
||||
|
@ -54,6 +56,7 @@ func _scan_test_suites(dir :DirAccess, collected_suites :Array[Node]) -> Array[N
|
|||
if exclude_scan_directories.has(dir.get_current_dir()):
|
||||
return collected_suites
|
||||
prints("Scanning for test suites in:", dir.get_current_dir())
|
||||
@warning_ignore("return_value_discarded")
|
||||
dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
|
||||
var file_name := dir.get_next()
|
||||
while file_name != "":
|
||||
|
@ -61,6 +64,7 @@ func _scan_test_suites(dir :DirAccess, collected_suites :Array[Node]) -> Array[N
|
|||
if dir.current_is_dir():
|
||||
var sub_dir := DirAccess.open(resource_path)
|
||||
if sub_dir != null:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_scan_test_suites(sub_dir, collected_suites)
|
||||
else:
|
||||
var time := LocalTime.now()
|
||||
|
@ -91,7 +95,7 @@ func _parse_is_test_suite(resource_path :String) -> Node:
|
|||
return null
|
||||
# Check in the global class cache whether the GdUnitTestSuite class has been extended.
|
||||
if _included_resources.has(resource_path):
|
||||
return _parse_test_suite(ResourceLoader.load(resource_path))
|
||||
return _parse_test_suite(GdUnitTestSuiteScanner.load_with_disabled_warnings(resource_path))
|
||||
|
||||
# Otherwise we need to scan manual, we need to exclude classes where direct extends form Godot classes
|
||||
# the resource loader can fail to load e.g. plugin classes with do preload other scripts
|
||||
|
@ -100,10 +104,25 @@ func _parse_is_test_suite(resource_path :String) -> Node:
|
|||
if extends_from.is_empty() or ClassDB.class_exists(extends_from):
|
||||
return null
|
||||
# Finally, we need to load the class to determine it is a test suite
|
||||
var script := ResourceLoader.load(resource_path)
|
||||
var script := GdUnitTestSuiteScanner.load_with_disabled_warnings(resource_path)
|
||||
if not GdObjects.is_test_suite(script):
|
||||
return null
|
||||
return _parse_test_suite(ResourceLoader.load(resource_path))
|
||||
return _parse_test_suite(script)
|
||||
|
||||
|
||||
# We load the test suites with disabled unsafe_method_access to avoid spamming loading errors
|
||||
# `unsafe_method_access` will happen when using `assert_that`
|
||||
static func load_with_disabled_warnings(resource_path: String) -> GDScript:
|
||||
# grap current level
|
||||
var unsafe_method_access: Variant = ProjectSettings.get_setting("debug/gdscript/warnings/unsafe_method_access")
|
||||
|
||||
# disable and load the script
|
||||
ProjectSettings.set_setting("debug/gdscript/warnings/unsafe_method_access", 0)
|
||||
var script: GDScript = ResourceLoader.load(resource_path)
|
||||
|
||||
# restore
|
||||
ProjectSettings.set_setting("debug/gdscript/warnings/unsafe_method_access", unsafe_method_access)
|
||||
return script
|
||||
|
||||
|
||||
static func _is_script_format_supported(resource_path :String) -> bool:
|
||||
|
@ -113,65 +132,58 @@ static func _is_script_format_supported(resource_path :String) -> bool:
|
|||
return GdUnit4CSharpApiLoader.is_csharp_file(resource_path)
|
||||
|
||||
|
||||
func _parse_test_suite(script :GDScript) -> GdUnitTestSuite:
|
||||
func _parse_test_suite(script: Script) -> GdUnitTestSuite:
|
||||
if not GdObjects.is_test_suite(script):
|
||||
return null
|
||||
|
||||
var test_suite :GdUnitTestSuite = script.new()
|
||||
# If test suite a C# script
|
||||
if GdUnit4CSharpApiLoader.is_test_suite(script.resource_path):
|
||||
return GdUnit4CSharpApiLoader.parse_test_suite(script.resource_path)
|
||||
|
||||
# Do pares as GDScript
|
||||
var test_suite: GdUnitTestSuite = (script as GDScript).new()
|
||||
test_suite.set_name(GdUnitTestSuiteScanner.parse_test_suite_name(script))
|
||||
# add test cases to test suite and parse test case line nummber
|
||||
var test_case_names := _extract_test_case_names(script)
|
||||
_parse_and_add_test_cases(test_suite, script, test_case_names)
|
||||
# not all test case parsed?
|
||||
# we have to scan the base class to
|
||||
if not test_case_names.is_empty():
|
||||
var base_script :GDScript = test_suite.get_script().get_base_script()
|
||||
while base_script is GDScript:
|
||||
# do not parse testsuite itself
|
||||
if base_script.resource_path.find("GdUnitTestSuite") == -1:
|
||||
_parse_and_add_test_cases(test_suite, base_script, test_case_names)
|
||||
base_script = base_script.get_base_script()
|
||||
var test_case_names := _extract_test_case_names(script as GDScript)
|
||||
_parse_and_add_test_cases(test_suite, script as GDScript, test_case_names)
|
||||
return test_suite
|
||||
|
||||
|
||||
func _extract_test_case_names(script :GDScript) -> PackedStringArray:
|
||||
var names := PackedStringArray()
|
||||
for method in script.get_script_method_list():
|
||||
var funcName :String = method["name"]
|
||||
if funcName.begins_with("test"):
|
||||
names.append(funcName)
|
||||
return names
|
||||
return script.get_script_method_list()\
|
||||
.map(func(descriptor: Dictionary) -> String: return descriptor["name"])\
|
||||
.filter(func(func_name: String) -> bool: return func_name.begins_with("test"))
|
||||
|
||||
|
||||
static func parse_test_suite_name(script :Script) -> String:
|
||||
return script.resource_path.get_file().replace(".gd", "")
|
||||
|
||||
|
||||
func _handle_test_suite_arguments(test_suite :Node, script :GDScript, fd :GdFunctionDescriptor) -> void:
|
||||
func _handle_test_suite_arguments(test_suite: GdUnitTestSuite, script: GDScript, fd: GdFunctionDescriptor) -> void:
|
||||
for arg in fd.args():
|
||||
match arg.name():
|
||||
_TestCase.ARGUMENT_SKIP:
|
||||
var result :Variant = _expression_runner.execute(script, arg.value_as_string())
|
||||
var result: Variant = _expression_runner.execute(script, arg.plain_value())
|
||||
if result is bool:
|
||||
test_suite.__is_skipped = result
|
||||
else:
|
||||
push_error("Test expression '%s' cannot be evaluated because it is not of type bool!" % arg.value_as_string())
|
||||
push_error("Test expression '%s' cannot be evaluated because it is not of type bool!" % arg.plain_value())
|
||||
_TestCase.ARGUMENT_SKIP_REASON:
|
||||
test_suite.__skip_reason = arg.value_as_string()
|
||||
test_suite.__skip_reason = arg.plain_value()
|
||||
_:
|
||||
push_error("Unsuported argument `%s` found on before() at '%s'!" % [arg.name(), script.resource_path])
|
||||
|
||||
|
||||
func _handle_test_case_arguments(test_suite :Node, script :GDScript, fd :GdFunctionDescriptor) -> void:
|
||||
func _handle_test_case_arguments(test_suite: GdUnitTestSuite, script: GDScript, fd: GdFunctionDescriptor) -> void:
|
||||
var timeout := _TestCase.DEFAULT_TIMEOUT
|
||||
var iterations := Fuzzer.ITERATION_DEFAULT_COUNT
|
||||
var seed_value := -1
|
||||
var is_skipped := false
|
||||
var skip_reason := "Unknown."
|
||||
var fuzzers :Array[GdFunctionArgument] = []
|
||||
var fuzzers: Array[GdFunctionArgument] = []
|
||||
var test := _TestCase.new()
|
||||
|
||||
for arg in fd.args():
|
||||
for arg: GdFunctionArgument in fd.args():
|
||||
# verify argument is allowed
|
||||
# is test using fuzzers?
|
||||
if arg.type() == GdObjects.TYPE_FUZZER:
|
||||
|
@ -181,31 +193,33 @@ func _handle_test_case_arguments(test_suite :Node, script :GDScript, fd :GdFunct
|
|||
_TestCase.ARGUMENT_TIMEOUT:
|
||||
timeout = arg.default()
|
||||
_TestCase.ARGUMENT_SKIP:
|
||||
var result :Variant = _expression_runner.execute(script, arg.value_as_string())
|
||||
var result :Variant = _expression_runner.execute(script, arg.plain_value())
|
||||
if result is bool:
|
||||
is_skipped = result
|
||||
else:
|
||||
push_error("Test expression '%s' cannot be evaluated because it is not of type bool!" % arg.value_as_string())
|
||||
push_error("Test expression '%s' cannot be evaluated because it is not of type bool!" % arg.plain_value())
|
||||
_TestCase.ARGUMENT_SKIP_REASON:
|
||||
skip_reason = arg.value_as_string()
|
||||
skip_reason = arg.plain_value()
|
||||
Fuzzer.ARGUMENT_ITERATIONS:
|
||||
iterations = arg.default()
|
||||
Fuzzer.ARGUMENT_SEED:
|
||||
seed_value = arg.default()
|
||||
# create new test
|
||||
test.configure(fd.name(), fd.line_number(), script.resource_path, timeout, fuzzers, iterations, seed_value)
|
||||
@warning_ignore("return_value_discarded")
|
||||
test.configure(fd.name(), fd.line_number(), fd.source_path(), timeout, fuzzers, iterations, seed_value)
|
||||
test.set_function_descriptor(fd)
|
||||
test.skip(is_skipped, skip_reason)
|
||||
_validate_argument(fd, test)
|
||||
test_suite.add_child(test)
|
||||
|
||||
|
||||
func _parse_and_add_test_cases(test_suite :Node, script :GDScript, test_case_names :PackedStringArray) -> void:
|
||||
func _parse_and_add_test_cases(test_suite: GdUnitTestSuite, script: GDScript, test_case_names: PackedStringArray) -> void:
|
||||
var test_cases_to_find := Array(test_case_names)
|
||||
var functions_to_scan := test_case_names.duplicate()
|
||||
@warning_ignore("return_value_discarded")
|
||||
functions_to_scan.append("before")
|
||||
var source := _script_parser.load_source_code(script, [script.resource_path])
|
||||
var function_descriptors := _script_parser.parse_functions(source, "", [script.resource_path], functions_to_scan)
|
||||
|
||||
var function_descriptors := _script_parser.get_function_descriptors(script, functions_to_scan)
|
||||
for fd in function_descriptors:
|
||||
if fd.name() == "before":
|
||||
_handle_test_suite_arguments(test_suite, script, fd)
|
||||
|
@ -226,7 +240,7 @@ func _validate_argument(fd :GdFunctionDescriptor, test_case :_TestCase) -> void:
|
|||
|
||||
# converts given file name by configured naming convention
|
||||
static func _to_naming_convention(file_name :String) -> String:
|
||||
var nc :int = GdUnitSettings.get_setting(GdUnitSettings.TEST_SITE_NAMING_CONVENTION, 0)
|
||||
var nc :int = GdUnitSettings.get_setting(GdUnitSettings.TEST_SUITE_NAMING_CONVENTION, 0)
|
||||
match nc:
|
||||
GdUnitSettings.NAMING_CONVENTIONS.AUTO_DETECT:
|
||||
if GdObjects.is_snake_case(file_name):
|
||||
|
@ -291,16 +305,15 @@ static func create_test_suite(test_suite_path :String, source_path :String) -> G
|
|||
static func get_test_case_line_number(resource_path :String, func_name :String) -> int:
|
||||
var file := FileAccess.open(resource_path, FileAccess.READ)
|
||||
if file != null:
|
||||
var script_parser := GdScriptParser.new()
|
||||
var line_number := 0
|
||||
while not file.eof_reached():
|
||||
var row := GdScriptParser.clean_up_row(file.get_line())
|
||||
var row := file.get_line()
|
||||
line_number += 1
|
||||
# ignore comments and empty lines and not test functions
|
||||
if row.begins_with("#") || row.length() == 0 || row.find("functest") == -1:
|
||||
if row.begins_with("#") || row.length() == 0 || row.find("func test_") == -1:
|
||||
continue
|
||||
# abort if test case name found
|
||||
if script_parser.parse_func_name(row) == "test_" + func_name:
|
||||
if row.find("func") != -1 and row.find("test_" + func_name) != -1:
|
||||
return line_number
|
||||
return -1
|
||||
|
||||
|
@ -323,7 +336,7 @@ func get_extends_classname(resource_path :String) -> String:
|
|||
|
||||
|
||||
static func add_test_case(resource_path :String, func_name :String) -> GdUnitResult:
|
||||
var script := load(resource_path) as GDScript
|
||||
var script := load_with_disabled_warnings(resource_path)
|
||||
# count all exiting lines and add two as space to add new test case
|
||||
var line_number := count_lines(script) + 2
|
||||
var func_body := TEST_FUNC_TEMPLATE.replace("${func_name}", func_name)
|
||||
|
@ -350,7 +363,7 @@ static func test_suite_exists(test_suite_path :String) -> bool:
|
|||
static func test_case_exists(test_suite_path :String, func_name :String) -> bool:
|
||||
if not test_suite_exists(test_suite_path):
|
||||
return false
|
||||
var script := ResourceLoader.load(test_suite_path) as GDScript
|
||||
var script := load_with_disabled_warnings(test_suite_path)
|
||||
for f in script.get_script_method_list():
|
||||
if f["name"] == "test_" + func_name:
|
||||
return true
|
||||
|
|
|
@ -27,11 +27,13 @@ static func prints_verbose(message :String) -> void:
|
|||
prints(message)
|
||||
|
||||
|
||||
static func free_instance(instance :Variant, is_stdout_verbose :=false) -> bool:
|
||||
@warning_ignore("unsafe_cast")
|
||||
static func free_instance(instance :Variant, use_call_deferred :bool = false, is_stdout_verbose := false) -> bool:
|
||||
if instance is Array:
|
||||
for element :Variant in instance:
|
||||
@warning_ignore("return_value_discarded")
|
||||
free_instance(element)
|
||||
instance.clear()
|
||||
(instance as Array).clear()
|
||||
return true
|
||||
# do not free an already freed instance
|
||||
if not is_instance_valid(instance):
|
||||
|
@ -41,26 +43,39 @@ static func free_instance(instance :Variant, is_stdout_verbose :=false) -> bool:
|
|||
return false
|
||||
if is_stdout_verbose:
|
||||
print_verbose("GdUnit4:gc():free instance ", instance)
|
||||
release_double(instance)
|
||||
release_double(instance as Object)
|
||||
if instance is RefCounted:
|
||||
instance.notification(Object.NOTIFICATION_PREDELETE)
|
||||
(instance as RefCounted).notification(Object.NOTIFICATION_PREDELETE)
|
||||
# If scene runner freed we explicit await all inputs are processed
|
||||
if instance is GdUnitSceneRunnerImpl:
|
||||
await (instance as GdUnitSceneRunnerImpl).await_input_processed()
|
||||
return true
|
||||
else:
|
||||
# is instance already freed?
|
||||
#if not is_instance_valid(instance) or ClassDB.class_get_property(instance, "new"):
|
||||
# return false
|
||||
#release_connections(instance)
|
||||
if instance is Timer:
|
||||
instance.stop()
|
||||
instance.call_deferred("free")
|
||||
await Engine.get_main_loop().process_frame
|
||||
var timer := instance as Timer
|
||||
timer.stop()
|
||||
if use_call_deferred:
|
||||
timer.call_deferred("free")
|
||||
else:
|
||||
timer.free()
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return true
|
||||
if instance is Node and instance.get_parent() != null:
|
||||
|
||||
if instance is Node and (instance as Node).get_parent() != null:
|
||||
var node := instance as Node
|
||||
if is_stdout_verbose:
|
||||
print_verbose("GdUnit4:gc():remove node from parent ", instance.get_parent(), instance)
|
||||
instance.get_parent().remove_child(instance)
|
||||
instance.set_owner(null)
|
||||
instance.free()
|
||||
print_verbose("GdUnit4:gc():remove node from parent ", node.get_parent(), node)
|
||||
if use_call_deferred:
|
||||
node.get_parent().remove_child.call_deferred(node)
|
||||
#instance.call_deferred("set_owner", null)
|
||||
else:
|
||||
node.get_parent().remove_child(node)
|
||||
if is_stdout_verbose:
|
||||
print_verbose("GdUnit4:gc():freeing `free()` the instance ", instance)
|
||||
if use_call_deferred:
|
||||
(instance as Object).call_deferred("free")
|
||||
else:
|
||||
(instance as Object).free()
|
||||
return !is_instance_valid(instance)
|
||||
|
||||
|
||||
|
@ -81,21 +96,22 @@ static func _release_connections(instance :Object) -> void:
|
|||
|
||||
static func release_timers() -> void:
|
||||
# we go the new way to hold all gdunit timers in group 'GdUnitTimers'
|
||||
if Engine.get_main_loop().root == null:
|
||||
var scene_tree := Engine.get_main_loop() as SceneTree
|
||||
if scene_tree.root == null:
|
||||
return
|
||||
for node :Node in Engine.get_main_loop().root.get_children():
|
||||
for node :Node in scene_tree.root.get_children():
|
||||
if is_instance_valid(node) and node.is_in_group("GdUnitTimers"):
|
||||
if is_instance_valid(node):
|
||||
Engine.get_main_loop().root.remove_child(node)
|
||||
node.stop()
|
||||
node.free()
|
||||
scene_tree.root.remove_child.call_deferred(node)
|
||||
(node as Timer).stop()
|
||||
node.queue_free()
|
||||
|
||||
|
||||
# the finally cleaup unfreed resources and singletons
|
||||
static func dispose_all() -> void:
|
||||
static func dispose_all(use_call_deferred :bool = false) -> void:
|
||||
release_timers()
|
||||
GdUnitSingleton.dispose(use_call_deferred)
|
||||
GdUnitSignals.dispose()
|
||||
GdUnitSingleton.dispose()
|
||||
|
||||
|
||||
# if instance an mock or spy we need manually freeing the self reference
|
||||
|
@ -104,12 +120,6 @@ static func release_double(instance :Object) -> void:
|
|||
instance.call("__release_double")
|
||||
|
||||
|
||||
static func clear_push_errors() -> void:
|
||||
var runner :Node = Engine.get_meta("GdUnitRunner")
|
||||
if runner != null:
|
||||
runner.clear_push_errors()
|
||||
|
||||
|
||||
static func register_expect_interupted_by_timeout(test_suite :Node, test_case_name :String) -> void:
|
||||
var test_case :Node = test_suite.find_child(test_case_name, false, false)
|
||||
var test_case: _TestCase = test_suite.find_child(test_case_name, false, false)
|
||||
test_case.expect_to_interupt()
|
||||
|
|
|
@ -19,11 +19,3 @@ static func set_event_global_position(event: InputEventMouseMotion, global_posit
|
|||
event.global_position = event.position
|
||||
else:
|
||||
event.global_position = global_position
|
||||
|
||||
|
||||
# we crash on macOS when using free() inside the plugin _exit_tree
|
||||
static func free_fix(instance: Object) -> void:
|
||||
if OS.get_distribution_name().contains("mac"):
|
||||
instance.queue_free()
|
||||
else:
|
||||
instance.free()
|
||||
|
|
|
@ -3,6 +3,7 @@ class_name LocalTime
|
|||
extends Resource
|
||||
|
||||
enum TimeUnit {
|
||||
DEFAULT = 0,
|
||||
MILLIS = 1,
|
||||
SECOND = 2,
|
||||
MINUTE = 3,
|
||||
|
@ -60,6 +61,7 @@ func plus(time_unit :TimeUnit, value :int) -> LocalTime:
|
|||
addValue = value * MILLIS_PER_MINUTE
|
||||
TimeUnit.HOUR:
|
||||
addValue = value * MILLIS_PER_HOUR
|
||||
@warning_ignore("return_value_discarded")
|
||||
_init(_time + addValue)
|
||||
return self
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ var _expect_to_interupt := false
|
|||
var _timer: Timer
|
||||
var _interupted: bool = false
|
||||
var _failed := false
|
||||
var _report: GdUnitReport = null
|
||||
var _parameter_set_resolver: GdUnitTestParameterSetResolver
|
||||
var _is_disposed := false
|
||||
|
||||
|
@ -80,14 +79,13 @@ func dispose() -> void:
|
|||
stop_timer()
|
||||
_remove_failure_handler()
|
||||
_fuzzers.clear()
|
||||
_report = null
|
||||
|
||||
|
||||
@warning_ignore("shadowed_variable_base_class", "redundant_await")
|
||||
func _execute_test_case(name: String, test_parameter: Array) -> void:
|
||||
# needs at least on await otherwise it breaks the awaiting chain
|
||||
await get_parent().callv(name, test_parameter)
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
completed.emit()
|
||||
|
||||
|
||||
|
@ -104,22 +102,30 @@ func set_timeout() -> void:
|
|||
_timer = Timer.new()
|
||||
add_child(_timer)
|
||||
_timer.set_name("gdunit_test_case_timer_%d" % _timer.get_instance_id())
|
||||
_timer.timeout.connect(func do_interrupt() -> void:
|
||||
if is_fuzzed():
|
||||
_report = GdUnitReport.new().create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.fuzzer_interuped(_current_iteration, "timedout"))
|
||||
else:
|
||||
_report = GdUnitReport.new().create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.test_timeout(timeout))
|
||||
_interupted = true
|
||||
completed.emit()
|
||||
, CONNECT_DEFERRED)
|
||||
@warning_ignore("return_value_discarded")
|
||||
_timer.timeout.connect(do_interrupt, CONNECT_DEFERRED)
|
||||
_timer.set_one_shot(true)
|
||||
_timer.set_wait_time(time)
|
||||
_timer.set_autostart(false)
|
||||
_timer.start()
|
||||
|
||||
|
||||
func do_interrupt() -> void:
|
||||
_interupted = true
|
||||
if not is_expect_interupted():
|
||||
var execution_context:= GdUnitThreadManager.get_current_context().get_execution_context()
|
||||
if is_fuzzed():
|
||||
execution_context.add_report(GdUnitReport.new()\
|
||||
.create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.fuzzer_interuped(_current_iteration, "timedout")))
|
||||
else:
|
||||
execution_context.add_report(GdUnitReport.new()\
|
||||
.create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.test_timeout(timeout)))
|
||||
completed.emit()
|
||||
|
||||
|
||||
func _set_failure_handler() -> void:
|
||||
if not GdUnitSignals.instance().gdunit_set_test_failed.is_connected(_failure_received):
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitSignals.instance().gdunit_set_test_failed.connect(_failure_received)
|
||||
|
||||
|
||||
|
@ -164,10 +170,6 @@ func is_skipped() -> bool:
|
|||
return _skipped
|
||||
|
||||
|
||||
func report() -> GdUnitReport:
|
||||
return _report
|
||||
|
||||
|
||||
func skip_info() -> String:
|
||||
return _skip_reason
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://btx5kcrsngasl"
|
||||
path="res://.godot/imported/touch-button.png-2fff40c8520d8e97a57db1b2b043f641.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/gdUnit4/src/core/assets/touch-button.png"
|
||||
dest_files=["res://.godot/imported/touch-button.png-2fff40c8520d8e97a57db1b2b043f641.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
|
@ -50,6 +50,7 @@ static func instance() -> GdUnitCommandHandler:
|
|||
return GdUnitSingleton.instance("GdUnitCommandHandler", func() -> GdUnitCommandHandler: return GdUnitCommandHandler.new())
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func _init() -> void:
|
||||
assert_shortcut_mappings(SETTINGS_SHORTCUT_MAPPING)
|
||||
|
||||
|
@ -58,6 +59,7 @@ func _init() -> void:
|
|||
GdUnitSignals.instance().gdunit_client_disconnected.connect(_on_client_disconnected)
|
||||
GdUnitSignals.instance().gdunit_settings_changed.connect(_on_settings_changed)
|
||||
# preload previous test execution
|
||||
@warning_ignore("return_value_discarded")
|
||||
_runner_config.load_config()
|
||||
|
||||
init_shortcuts()
|
||||
|
@ -73,13 +75,10 @@ func _init() -> void:
|
|||
register_command(GdUnitCommand.new(CMD_CREATE_TESTCASE, is_not_running, cmd_create_test, GdUnitShortcut.ShortCut.CREATE_TEST))
|
||||
register_command(GdUnitCommand.new(CMD_STOP_TEST_RUN, is_running, cmd_stop.bind(_client_id), GdUnitShortcut.ShortCut.STOP_TEST_RUN))
|
||||
|
||||
|
||||
# do not reschedule inside of test run (called on GdUnitCommandHandlerTest)
|
||||
if Engine.has_meta("GdUnitRunner"):
|
||||
return
|
||||
# schedule discover tests if enabled
|
||||
if GdUnitSettings.is_test_discover_enabled():
|
||||
var timer :SceneTreeTimer = Engine.get_main_loop().create_timer(5)
|
||||
# schedule discover tests if enabled and running inside the editor
|
||||
if Engine.is_editor_hint() and GdUnitSettings.is_test_discover_enabled():
|
||||
var timer :SceneTreeTimer = (Engine.get_main_loop() as SceneTree).create_timer(5)
|
||||
@warning_ignore("return_value_discarded")
|
||||
timer.timeout.connect(cmd_discover_tests)
|
||||
|
||||
|
||||
|
@ -123,7 +122,7 @@ func init_shortcuts() -> void:
|
|||
register_shortcut(shortcut, inputEvent)
|
||||
|
||||
|
||||
func create_shortcut_input_even(key_codes : PackedInt32Array) -> InputEventKey:
|
||||
func create_shortcut_input_even(key_codes: PackedInt32Array) -> InputEventKey:
|
||||
var inputEvent :InputEventKey = InputEventKey.new()
|
||||
inputEvent.pressed = true
|
||||
for key_code in key_codes:
|
||||
|
@ -208,7 +207,8 @@ func cmd_run(debug :bool) -> void:
|
|||
if _is_running:
|
||||
return
|
||||
# save current selected excution config
|
||||
var result := _runner_config.set_server_port(Engine.get_meta("gdunit_server_port")).save_config()
|
||||
var server_port: int = Engine.get_meta("gdunit_server_port")
|
||||
var result := _runner_config.set_server_port(server_port).save_config()
|
||||
if result.is_error():
|
||||
push_error(result.error_message())
|
||||
return
|
||||
|
@ -232,9 +232,10 @@ func cmd_stop(client_id :int) -> void:
|
|||
if _running_debug_mode:
|
||||
EditorInterface.stop_playing_scene()
|
||||
else: if _current_runner_process_id > 0:
|
||||
var result := OS.kill(_current_runner_process_id)
|
||||
if result != OK:
|
||||
push_error("ERROR checked stopping GdUnit Test Runner. error code: %s" % result)
|
||||
if OS.is_process_running(_current_runner_process_id):
|
||||
var result := OS.kill(_current_runner_process_id)
|
||||
if result != OK:
|
||||
push_error("ERROR checked stopping GdUnit Test Runner. error code: %s" % result)
|
||||
_current_runner_process_id = -1
|
||||
|
||||
|
||||
|
@ -242,6 +243,7 @@ func cmd_editor_run_test(debug :bool) -> void:
|
|||
var cursor_line := active_base_editor().get_caret_line()
|
||||
#run test case?
|
||||
var regex := RegEx.new()
|
||||
@warning_ignore("return_value_discarded")
|
||||
regex.compile("(^func[ ,\t])(test_[a-zA-Z0-9_]*)")
|
||||
var result := regex.search(active_base_editor().get_line(cursor_line))
|
||||
if result:
|
||||
|
@ -262,8 +264,10 @@ func cmd_create_test() -> void:
|
|||
# show error dialog
|
||||
push_error("Failed to create test case: %s" % result.error_message())
|
||||
return
|
||||
var info := result.value() as Dictionary
|
||||
ScriptEditorControls.edit_script(info.get("path"), info.get("line"))
|
||||
var info: Dictionary = result.value()
|
||||
var script_path: String = info.get("path")
|
||||
var script_line: int = info.get("line")
|
||||
ScriptEditorControls.edit_script(script_path, script_line)
|
||||
|
||||
|
||||
func cmd_discover_tests() -> void:
|
||||
|
@ -279,8 +283,10 @@ static func scan_test_directorys(base_directory :String, test_directory: String,
|
|||
if GdUnitTestSuiteScanner.exclude_scan_directories.has(current_directory):
|
||||
continue
|
||||
if match_test_directory(directory, test_directory):
|
||||
@warning_ignore("return_value_discarded")
|
||||
test_suite_paths.append(current_directory)
|
||||
else:
|
||||
@warning_ignore("return_value_discarded")
|
||||
scan_test_directorys(current_directory, test_directory, test_suite_paths)
|
||||
return test_suite_paths
|
||||
|
||||
|
@ -342,11 +348,13 @@ func _on_run_overall_pressed(_debug := false) -> void:
|
|||
func _on_settings_changed(property :GdUnitProperty) -> void:
|
||||
if SETTINGS_SHORTCUT_MAPPING.has(property.name()):
|
||||
var shortcut :GdUnitShortcut.ShortCut = SETTINGS_SHORTCUT_MAPPING.get(property.name())
|
||||
var input_event := create_shortcut_input_even(property.value())
|
||||
var value: PackedInt32Array = property.value()
|
||||
var input_event := create_shortcut_input_even(value)
|
||||
prints("Shortcut changed: '%s' to '%s'" % [GdUnitShortcut.ShortCut.keys()[shortcut], input_event.as_text()])
|
||||
register_shortcut(shortcut, input_event)
|
||||
if property.name() == GdUnitSettings.TEST_DISCOVER_ENABLED:
|
||||
var timer :SceneTreeTimer = Engine.get_main_loop().create_timer(3)
|
||||
var timer :SceneTreeTimer = (Engine.get_main_loop() as SceneTree).create_timer(3)
|
||||
@warning_ignore("return_value_discarded")
|
||||
timer.timeout.connect(cmd_discover_tests)
|
||||
|
||||
|
||||
|
|
|
@ -1,60 +1,105 @@
|
|||
extends RefCounted
|
||||
|
||||
|
||||
# Caches all test indices for parameterized tests
|
||||
class TestCaseIndicesCache:
|
||||
var _cache := {}
|
||||
|
||||
func _key(resource_path: String, test_name: String) -> StringName:
|
||||
return &"%s_%s" % [resource_path, test_name]
|
||||
|
||||
|
||||
func contains_test_case(resource_path: String, test_name: String) -> bool:
|
||||
return _cache.has(_key(resource_path, test_name))
|
||||
|
||||
|
||||
func validate(resource_path: String, test_name: String, indices: PackedStringArray) -> bool:
|
||||
var cached_indicies: PackedStringArray = _cache[_key(resource_path, test_name)]
|
||||
return GdArrayTools.has_same_content(cached_indicies, indices)
|
||||
|
||||
|
||||
func sync(resource_path: String, test_name: String, indices: PackedStringArray) -> void:
|
||||
if indices.is_empty():
|
||||
_cache[_key(resource_path, test_name)] = []
|
||||
else:
|
||||
_cache[_key(resource_path, test_name)] = indices
|
||||
|
||||
# contains all tracked test suites where discovered since editor start
|
||||
# key : test suite resource_path
|
||||
# value: the list of discovered test case names
|
||||
var _discover_cache := {}
|
||||
|
||||
var discovered_test_case_indices_cache := TestCaseIndicesCache.new()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# Register for discovery events to sync the cache
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitSignals.instance().gdunit_add_test_suite.connect(sync_cache)
|
||||
|
||||
|
||||
func sync_cache(dto :GdUnitTestSuiteDto) -> void:
|
||||
var resource_path := dto.path()
|
||||
var discovered_test_cases :Array[String] = []
|
||||
func sync_cache(dto: GdUnitTestSuiteDto) -> void:
|
||||
var resource_path := ProjectSettings.localize_path(dto.path())
|
||||
var discovered_test_cases: Array[String] = []
|
||||
for test_case in dto.test_cases():
|
||||
discovered_test_cases.append(test_case.name())
|
||||
discovered_test_case_indices_cache.sync(resource_path, test_case.name(), test_case.test_case_names())
|
||||
_discover_cache[resource_path] = discovered_test_cases
|
||||
|
||||
|
||||
func discover(script: Script) -> void:
|
||||
# for cs scripts we need to recomplie before discover new tests
|
||||
if GdObjects.is_cs_script(script):
|
||||
await rebuild_project(script)
|
||||
|
||||
if GdObjects.is_test_suite(script):
|
||||
# a new test suite is discovered
|
||||
if not _discover_cache.has(script.resource_path):
|
||||
var scanner := GdUnitTestSuiteScanner.new()
|
||||
var test_suite := scanner._parse_test_suite(script)
|
||||
var script_path := ProjectSettings.localize_path(script.resource_path)
|
||||
var scanner := GdUnitTestSuiteScanner.new()
|
||||
var test_suite := scanner._parse_test_suite(script)
|
||||
var suite_name := test_suite.get_name()
|
||||
|
||||
if not _discover_cache.has(script_path):
|
||||
var dto :GdUnitTestSuiteDto = GdUnitTestSuiteDto.of(test_suite)
|
||||
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestSuiteAdded.new(script.resource_path, test_suite.get_name(), dto))
|
||||
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestSuiteAdded.new(script_path, suite_name, dto))
|
||||
sync_cache(dto)
|
||||
test_suite.queue_free()
|
||||
return
|
||||
|
||||
var tests_added :Array[String] = []
|
||||
var tests_removed := PackedStringArray()
|
||||
var script_test_cases := extract_test_functions(script)
|
||||
var discovered_test_cases :Array[String] = _discover_cache.get(script.resource_path, [] as Array[String])
|
||||
var discovered_test_cases :Array[String] = _discover_cache.get(script_path, [] as Array[String])
|
||||
var script_test_cases := extract_test_functions(test_suite)
|
||||
|
||||
# first detect removed/renamed tests
|
||||
var tests_removed := PackedStringArray()
|
||||
for test_case in discovered_test_cases:
|
||||
if not script_test_cases.has(test_case):
|
||||
@warning_ignore("return_value_discarded")
|
||||
tests_removed.append(test_case)
|
||||
# second detect new added tests
|
||||
var tests_added :Array[String] = []
|
||||
for test_case in script_test_cases:
|
||||
if not discovered_test_cases.has(test_case):
|
||||
tests_added.append(test_case)
|
||||
|
||||
# We need to scan for parameterized test because of possible test data changes
|
||||
# For more details look at https://github.com/MikeSchulze/gdUnit4/issues/592
|
||||
for test_case_name in script_test_cases:
|
||||
if discovered_test_case_indices_cache.contains_test_case(script_path, test_case_name):
|
||||
var test_case: _TestCase = test_suite.find_child(test_case_name, false, false)
|
||||
var test_indices := test_case.test_case_names()
|
||||
if not discovered_test_case_indices_cache.validate(script_path, test_case_name, test_indices):
|
||||
if !tests_removed.has(test_case_name):
|
||||
tests_removed.append(test_case_name)
|
||||
if !tests_added.has(test_case_name):
|
||||
tests_added.append(test_case_name)
|
||||
discovered_test_case_indices_cache.sync(script_path, test_case_name, test_indices)
|
||||
|
||||
# finally notify changes to the inspector
|
||||
if not tests_removed.is_empty() or not tests_added.is_empty():
|
||||
var scanner := GdUnitTestSuiteScanner.new()
|
||||
var test_suite := scanner._parse_test_suite(script)
|
||||
var suite_name := test_suite.get_name()
|
||||
|
||||
# emit deleted tests
|
||||
for test_name in tests_removed:
|
||||
discovered_test_cases.erase(test_name)
|
||||
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestRemoved.new(script.resource_path, suite_name, test_name))
|
||||
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestRemoved.new(script_path, suite_name, test_name))
|
||||
|
||||
# emit new discovered tests
|
||||
for test_name in tests_added:
|
||||
|
@ -62,25 +107,46 @@ func discover(script: Script) -> void:
|
|||
var test_case := test_suite.find_child(test_name, false, false)
|
||||
var dto := GdUnitTestCaseDto.new()
|
||||
dto = dto.deserialize(dto.serialize(test_case))
|
||||
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestAdded.new(script.resource_path, suite_name, dto))
|
||||
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverTestAdded.new(script_path, suite_name, dto))
|
||||
# if the parameterized test fresh added we need to sync the cache
|
||||
if not discovered_test_case_indices_cache.contains_test_case(script_path, test_name):
|
||||
discovered_test_case_indices_cache.sync(script_path, test_name, dto.test_case_names())
|
||||
|
||||
# update the cache
|
||||
_discover_cache[script.resource_path] = discovered_test_cases
|
||||
_discover_cache[script_path] = discovered_test_cases
|
||||
test_suite.queue_free()
|
||||
|
||||
|
||||
func extract_test_functions(script :Script) -> PackedStringArray:
|
||||
return script.get_script_method_list()\
|
||||
.map(map_func_names)\
|
||||
.filter(filter_test_cases)
|
||||
func extract_test_functions(test_suite :Node) -> PackedStringArray:
|
||||
return test_suite.get_children()\
|
||||
.filter(func(child: Node) -> bool: return is_instance_of(child, _TestCase))\
|
||||
.map(func (child: Node) -> String: return child.get_name())
|
||||
|
||||
|
||||
func map_func_names(method_info :Dictionary) -> String:
|
||||
return method_info["name"]
|
||||
func is_paramaterized_test(test_suite :Node, test_case_name: String) -> bool:
|
||||
return test_suite.get_children()\
|
||||
.filter(func(child: Node) -> bool: return child.name == test_case_name)\
|
||||
.any(func (test: _TestCase) -> bool: return test.is_parameterized())
|
||||
|
||||
|
||||
func filter_test_cases(value :String) -> bool:
|
||||
return value.begins_with("test_")
|
||||
# do rebuild the entire project, there is actual no way to enforce the Godot engine itself to do this
|
||||
func rebuild_project(script: Script) -> void:
|
||||
var class_path := ProjectSettings.globalize_path(script.resource_path)
|
||||
print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard: CSharpScript change detected on: '%s' [/color]" % class_path)
|
||||
var scene_tree := Engine.get_main_loop() as SceneTree
|
||||
await scene_tree.process_frame
|
||||
|
||||
var output := []
|
||||
var exit_code := OS.execute("dotnet", ["--version"], output)
|
||||
if exit_code == -1:
|
||||
print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard:[/color] [color=RED]Rebuild the project failed.[/color]")
|
||||
print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard:[/color] [color=RED]Can't find installed `dotnet`! Please check your environment is setup correctly.[/color]")
|
||||
return
|
||||
print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard:[/color] [color=DEEP_SKY_BLUE]Found dotnet v%s[/color]" % output[0].strip_edges())
|
||||
output.clear()
|
||||
|
||||
func filter_by_test_cases(method_info :Dictionary, value :String) -> bool:
|
||||
return method_info["name"] == value
|
||||
exit_code = OS.execute("dotnet", ["build"], output)
|
||||
print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard:[/color] [color=DEEP_SKY_BLUE]Rebuild the project ... [/color]")
|
||||
for out:Variant in output:
|
||||
print_rich("[color=DEEP_SKY_BLUE] %s" % out.strip_edges())
|
||||
await scene_tree.process_frame
|
||||
|
|
|
@ -5,21 +5,34 @@ extends RefCounted
|
|||
static func run() -> void:
|
||||
prints("Running test discovery ..")
|
||||
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverStart.new())
|
||||
await Engine.get_main_loop().create_timer(.5).timeout
|
||||
await (Engine.get_main_loop() as SceneTree).create_timer(.5).timeout
|
||||
|
||||
var test_suite_directories :PackedStringArray = GdUnitCommandHandler.scan_test_directorys("res://" , GdUnitSettings.test_root_folder(), [])
|
||||
var scanner := GdUnitTestSuiteScanner.new()
|
||||
var _test_suites_to_process :Array[Node] = []
|
||||
for test_suite_dir in test_suite_directories:
|
||||
_test_suites_to_process.append_array(scanner.scan(test_suite_dir))
|
||||
# We run the test discovery in an extra thread so that the main thread is not blocked
|
||||
var t:= Thread.new()
|
||||
@warning_ignore("return_value_discarded")
|
||||
t.start(func () -> void:
|
||||
var test_suite_directories :PackedStringArray = GdUnitCommandHandler.scan_test_directorys("res://" , GdUnitSettings.test_root_folder(), [])
|
||||
var scanner := GdUnitTestSuiteScanner.new()
|
||||
var _test_suites_to_process :Array[Node] = []
|
||||
|
||||
var test_case_count :int = _test_suites_to_process.reduce(func (accum :int, test_suite :Node) -> int:
|
||||
return accum + test_suite.get_child_count(), 0)
|
||||
for test_suite_dir in test_suite_directories:
|
||||
_test_suites_to_process.append_array(scanner.scan(test_suite_dir))
|
||||
|
||||
for test_suite in _test_suites_to_process:
|
||||
var ts_dto := GdUnitTestSuiteDto.of(test_suite)
|
||||
GdUnitSignals.instance().gdunit_add_test_suite.emit(ts_dto)
|
||||
# Do sync the main thread before emit the discovered test suites to the inspector
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
var test_case_count :int = 0
|
||||
for test_suite in _test_suites_to_process:
|
||||
test_case_count += test_suite.get_child_count()
|
||||
var ts_dto := GdUnitTestSuiteDto.of(test_suite)
|
||||
GdUnitSignals.instance().gdunit_add_test_suite.emit(ts_dto)
|
||||
test_suite.free()
|
||||
|
||||
prints("%d test suites discovered." % _test_suites_to_process.size())
|
||||
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverEnd.new(_test_suites_to_process.size(), test_case_count))
|
||||
await Engine.get_main_loop().process_frame
|
||||
prints("%d test suites discovered." % _test_suites_to_process.size())
|
||||
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverEnd.new(_test_suites_to_process.size(), test_case_count))
|
||||
_test_suites_to_process.clear()
|
||||
)
|
||||
# wait unblocked to the tread is finished
|
||||
while t.is_alive():
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
# needs finally to wait for finish
|
||||
await t.wait_to_finish()
|
||||
|
|
|
@ -3,6 +3,7 @@ extends Resource
|
|||
|
||||
const WARNINGS = "warnings"
|
||||
const FAILED = "failed"
|
||||
const FLAKY = "flaky"
|
||||
const ERRORS = "errors"
|
||||
const SKIPPED = "skipped"
|
||||
const ELAPSED_TIME = "elapsed_time"
|
||||
|
@ -10,6 +11,7 @@ const ORPHAN_NODES = "orphan_nodes"
|
|||
const ERROR_COUNT = "error_count"
|
||||
const FAILED_COUNT = "failed_count"
|
||||
const SKIPPED_COUNT = "skipped_count"
|
||||
const RETRY_COUNT = "retry_count"
|
||||
|
||||
enum {
|
||||
INIT,
|
||||
|
@ -18,6 +20,7 @@ enum {
|
|||
TESTSUITE_AFTER,
|
||||
TESTCASE_BEFORE,
|
||||
TESTCASE_AFTER,
|
||||
TESTCASE_STATISTICS,
|
||||
DISCOVER_START,
|
||||
DISCOVER_END,
|
||||
DISCOVER_SUITE_ADDED,
|
||||
|
@ -71,6 +74,15 @@ func test_after(p_resource_path :String, p_suite_name :String, p_test_name :Stri
|
|||
return self
|
||||
|
||||
|
||||
func test_statistics(p_resource_path :String, p_suite_name :String, p_test_name :String, p_statistics :Dictionary = {}) -> GdUnitEvent:
|
||||
_event_type = TESTCASE_STATISTICS
|
||||
_resource_path = p_resource_path
|
||||
_suite_name = p_suite_name
|
||||
_test_name = p_test_name
|
||||
_statistics = p_statistics
|
||||
return self
|
||||
|
||||
|
||||
func type() -> int:
|
||||
return _event_type
|
||||
|
||||
|
@ -135,6 +147,10 @@ func is_error() -> bool:
|
|||
return _statistics.get(ERRORS, false)
|
||||
|
||||
|
||||
func is_flaky() -> bool:
|
||||
return _statistics.get(FLAKY, false)
|
||||
|
||||
|
||||
func is_skipped() -> bool:
|
||||
return _statistics.get(SKIPPED, false)
|
||||
|
||||
|
@ -170,7 +186,8 @@ func deserialize(serialized :Dictionary) -> GdUnitEvent:
|
|||
if serialized.has("reports"):
|
||||
# needs this workaround to copy typed values in the array
|
||||
var reports_to_deserializ :Array[Dictionary] = []
|
||||
reports_to_deserializ.append_array(serialized.get("reports"))
|
||||
@warning_ignore("unsafe_cast")
|
||||
reports_to_deserializ.append_array(serialized.get("reports") as Array)
|
||||
_reports = _deserialize_reports(reports_to_deserializ)
|
||||
return self
|
||||
|
||||
|
|
|
@ -2,50 +2,65 @@
|
|||
## It contains all the necessary information about the executed stage, such as memory observers, reports, orphan monitor
|
||||
class_name GdUnitExecutionContext
|
||||
|
||||
var _parent_context :GdUnitExecutionContext
|
||||
var _sub_context :Array[GdUnitExecutionContext] = []
|
||||
var _orphan_monitor :GdUnitOrphanNodesMonitor
|
||||
var _memory_observer :GdUnitMemoryObserver
|
||||
var _report_collector :GdUnitTestReportCollector
|
||||
var _timer :LocalTime
|
||||
var _parent_context: GdUnitExecutionContext
|
||||
var _sub_context: Array[GdUnitExecutionContext] = []
|
||||
var _orphan_monitor: GdUnitOrphanNodesMonitor
|
||||
var _memory_observer: GdUnitMemoryObserver
|
||||
var _report_collector: GdUnitTestReportCollector
|
||||
var _timer: LocalTime
|
||||
var _test_case_name: StringName
|
||||
var _name :String
|
||||
var _test_case_parameter_set: Array
|
||||
var _name: String
|
||||
var _test_execution_iteration: int = 0
|
||||
var _flaky_test_check := GdUnitSettings.is_test_flaky_check_enabled()
|
||||
var _flaky_test_retries := GdUnitSettings.get_flaky_max_retries()
|
||||
|
||||
|
||||
var error_monitor : GodotGdErrorMonitor = null:
|
||||
set (value):
|
||||
error_monitor = value
|
||||
# execution states
|
||||
var _is_calculated := false
|
||||
var _is_success: bool
|
||||
var _is_flaky: bool
|
||||
var _is_skipped: bool
|
||||
var _has_warnings: bool
|
||||
var _has_failures: bool
|
||||
var _has_errors: bool
|
||||
var _failure_count := 0
|
||||
var _orphan_count := 0
|
||||
var _error_count := 0
|
||||
var _skipped_count := 0
|
||||
|
||||
|
||||
var error_monitor: GodotGdErrorMonitor = null:
|
||||
get:
|
||||
if _parent_context != null:
|
||||
return _parent_context.error_monitor
|
||||
if error_monitor == null:
|
||||
error_monitor = GodotGdErrorMonitor.new()
|
||||
return error_monitor
|
||||
|
||||
|
||||
var test_suite : GdUnitTestSuite = null:
|
||||
set (value):
|
||||
test_suite = value
|
||||
var test_suite: GdUnitTestSuite = null:
|
||||
get:
|
||||
if _parent_context != null:
|
||||
return _parent_context.test_suite
|
||||
return test_suite
|
||||
|
||||
|
||||
var test_case : _TestCase = null:
|
||||
var test_case: _TestCase = null:
|
||||
get:
|
||||
if _test_case_name.is_empty():
|
||||
return null
|
||||
return test_suite.find_child(_test_case_name, false, false)
|
||||
if test_case == null and _parent_context != null:
|
||||
return _parent_context.test_case
|
||||
return test_case
|
||||
|
||||
|
||||
func _init(name :String, parent_context :GdUnitExecutionContext = null) -> void:
|
||||
func _init(name: StringName, parent_context: GdUnitExecutionContext = null) -> void:
|
||||
_name = name
|
||||
_parent_context = parent_context
|
||||
_timer = LocalTime.now()
|
||||
_orphan_monitor = GdUnitOrphanNodesMonitor.new(name)
|
||||
_orphan_monitor.start()
|
||||
_memory_observer = GdUnitMemoryObserver.new()
|
||||
error_monitor = GodotGdErrorMonitor.new()
|
||||
_report_collector = GdUnitTestReportCollector.new(get_instance_id())
|
||||
_report_collector = GdUnitTestReportCollector.new()
|
||||
if parent_context != null:
|
||||
parent_context._sub_context.append(self)
|
||||
|
||||
|
@ -58,40 +73,55 @@ func dispose() -> void:
|
|||
_parent_context = null
|
||||
test_suite = null
|
||||
test_case = null
|
||||
dispose_sub_contexts()
|
||||
|
||||
|
||||
func dispose_sub_contexts() -> void:
|
||||
for context in _sub_context:
|
||||
context.dispose()
|
||||
_sub_context.clear()
|
||||
|
||||
|
||||
func set_active() -> void:
|
||||
test_suite.__execution_context = self
|
||||
GdUnitThreadManager.get_current_context().set_execution_context(self)
|
||||
|
||||
|
||||
static func of_test_suite(test_suite_ :GdUnitTestSuite) -> GdUnitExecutionContext:
|
||||
assert(test_suite_, "test_suite is null")
|
||||
var context := GdUnitExecutionContext.new(test_suite_.get_name())
|
||||
context.test_suite = test_suite_
|
||||
context.set_active()
|
||||
return context
|
||||
|
||||
|
||||
static func of_test_case(pe :GdUnitExecutionContext, test_case_name :StringName) -> GdUnitExecutionContext:
|
||||
var context := GdUnitExecutionContext.new(test_case_name, pe)
|
||||
context._test_case_name = test_case_name
|
||||
context.set_active()
|
||||
return context
|
||||
|
||||
|
||||
static func of(pe :GdUnitExecutionContext) -> GdUnitExecutionContext:
|
||||
static func of(pe: GdUnitExecutionContext) -> GdUnitExecutionContext:
|
||||
var context := GdUnitExecutionContext.new(pe._test_case_name, pe)
|
||||
context._test_case_name = pe._test_case_name
|
||||
context.set_active()
|
||||
context._test_execution_iteration = pe._test_execution_iteration
|
||||
return context
|
||||
|
||||
|
||||
func test_failed() -> bool:
|
||||
return has_failures() or has_errors()
|
||||
static func of_test_suite(p_test_suite: GdUnitTestSuite) -> GdUnitExecutionContext:
|
||||
assert(p_test_suite, "test_suite is null")
|
||||
var context := GdUnitExecutionContext.new(p_test_suite.get_name())
|
||||
context.test_suite = p_test_suite
|
||||
return context
|
||||
|
||||
|
||||
static func of_test_case(pe: GdUnitExecutionContext, p_test_case: _TestCase) -> GdUnitExecutionContext:
|
||||
assert(p_test_case, "test_case is null")
|
||||
var context := GdUnitExecutionContext.new(p_test_case.get_name(), pe)
|
||||
context.test_case = p_test_case
|
||||
return context
|
||||
|
||||
|
||||
static func of_parameterized_test(pe: GdUnitExecutionContext, test_case_name: String, test_case_parameter_set: Array) -> GdUnitExecutionContext:
|
||||
var context := GdUnitExecutionContext.new(test_case_name, pe)
|
||||
context._test_case_name = test_case_name
|
||||
context._test_case_parameter_set = test_case_parameter_set
|
||||
return context
|
||||
|
||||
|
||||
func get_test_suite_path() -> String:
|
||||
return test_suite.get_script().resource_path
|
||||
|
||||
|
||||
func get_test_suite_name() -> StringName:
|
||||
return test_suite.get_name()
|
||||
|
||||
|
||||
func get_test_case_name() -> StringName:
|
||||
if _test_case_name.is_empty():
|
||||
return test_case.get_name()
|
||||
return _test_case_name
|
||||
|
||||
|
||||
func error_monitor_start() -> void:
|
||||
|
@ -102,7 +132,7 @@ func error_monitor_stop() -> void:
|
|||
await error_monitor.scan()
|
||||
for error_report in error_monitor.to_reports():
|
||||
if error_report.is_error():
|
||||
_report_collector._reports.append(error_report)
|
||||
_report_collector.push_back(error_report)
|
||||
|
||||
|
||||
func orphan_monitor_start() -> void:
|
||||
|
@ -113,45 +143,164 @@ func orphan_monitor_stop() -> void:
|
|||
_orphan_monitor.stop()
|
||||
|
||||
|
||||
func add_report(report: GdUnitReport) -> void:
|
||||
_report_collector.push_back(report)
|
||||
|
||||
|
||||
func reports() -> Array[GdUnitReport]:
|
||||
return _report_collector.reports()
|
||||
|
||||
|
||||
func build_report_statistics(orphans :int, recursive := true) -> Dictionary:
|
||||
func collect_reports(recursive: bool) -> Array[GdUnitReport]:
|
||||
if not recursive:
|
||||
return reports()
|
||||
var current_reports := reports()
|
||||
# we combine the reports of test_before(), test_after() and test() to be reported by `fire_test_ended`
|
||||
for sub_context in _sub_context:
|
||||
current_reports.append_array(sub_context.reports())
|
||||
# needs finally to clean the test reports to avoid counting twice
|
||||
sub_context.reports().clear()
|
||||
return current_reports
|
||||
|
||||
|
||||
func collect_orphans(p_reports: Array[GdUnitReport]) -> int:
|
||||
var orphans := 0
|
||||
if not _sub_context.is_empty():
|
||||
orphans += collect_testcase_orphan_reports(_sub_context[0], p_reports)
|
||||
orphans += collect_teststage_orphan_reports(p_reports)
|
||||
return orphans
|
||||
|
||||
|
||||
func collect_testcase_orphan_reports(context: GdUnitExecutionContext, p_reports: Array[GdUnitReport]) -> int:
|
||||
var orphans := context.count_orphans()
|
||||
if orphans > 0:
|
||||
p_reports.push_front(GdUnitReport.new()\
|
||||
.create(GdUnitReport.WARN, context.test_case.line_number(), GdAssertMessages.orphan_detected_on_test(orphans)))
|
||||
return orphans
|
||||
|
||||
|
||||
func collect_teststage_orphan_reports(p_reports: Array[GdUnitReport]) -> int:
|
||||
var orphans := count_orphans()
|
||||
if orphans > 0:
|
||||
p_reports.push_front(GdUnitReport.new()\
|
||||
.create(GdUnitReport.WARN, test_case.line_number(), GdAssertMessages.orphan_detected_on_test_setup(orphans)))
|
||||
return orphans
|
||||
|
||||
|
||||
func build_reports(recursive:= true) -> Array[GdUnitReport]:
|
||||
var collected_reports: Array[GdUnitReport] = collect_reports(recursive)
|
||||
if recursive:
|
||||
_orphan_count = collect_orphans(collected_reports)
|
||||
else:
|
||||
_orphan_count = count_orphans()
|
||||
if _orphan_count > 0:
|
||||
collected_reports.push_front(GdUnitReport.new() \
|
||||
.create(GdUnitReport.WARN, 1, GdAssertMessages.orphan_detected_on_suite_setup(_orphan_count)))
|
||||
_is_skipped = is_skipped()
|
||||
_skipped_count = count_skipped(recursive)
|
||||
_is_success = is_success()
|
||||
_is_flaky = is_flaky()
|
||||
_has_warnings = has_warnings()
|
||||
_has_errors = has_errors()
|
||||
_error_count = count_errors(recursive)
|
||||
if !_is_success:
|
||||
_has_failures = has_failures()
|
||||
_failure_count = count_failures(recursive)
|
||||
_is_calculated = true
|
||||
return collected_reports
|
||||
|
||||
|
||||
# Evaluates the actual test case status by validate latest execution state (cold be more based on flaky max retry count)
|
||||
func evaluate_test_retry_status() -> bool:
|
||||
# get latest test execution status
|
||||
var last_test_status :GdUnitExecutionContext = _sub_context.back()
|
||||
_is_skipped = last_test_status.is_skipped()
|
||||
_skipped_count = last_test_status.count_skipped(false)
|
||||
_is_success = last_test_status.is_success()
|
||||
# if success but it have more than one sub contexts the test was rerurn becouse of failures and will be marked as flaky
|
||||
_is_flaky = _is_success and _sub_context.size() > 1
|
||||
_has_warnings = last_test_status.has_warnings()
|
||||
_has_errors = last_test_status.has_errors()
|
||||
_error_count = last_test_status.count_errors(false)
|
||||
_has_failures = last_test_status.has_failures()
|
||||
_failure_count = last_test_status.count_failures(false)
|
||||
_orphan_count = last_test_status.collect_orphans(collect_reports(false))
|
||||
_is_calculated = true
|
||||
# finally cleanup the retry execution contexts
|
||||
dispose_sub_contexts()
|
||||
return _is_success
|
||||
|
||||
|
||||
func get_execution_statistics() -> Dictionary:
|
||||
return {
|
||||
GdUnitEvent.ORPHAN_NODES: orphans,
|
||||
GdUnitEvent.RETRY_COUNT: _test_execution_iteration,
|
||||
GdUnitEvent.ORPHAN_NODES: _orphan_count,
|
||||
GdUnitEvent.ELAPSED_TIME: _timer.elapsed_since_ms(),
|
||||
GdUnitEvent.FAILED: has_failures(),
|
||||
GdUnitEvent.ERRORS: has_errors(),
|
||||
GdUnitEvent.WARNINGS: has_warnings(),
|
||||
GdUnitEvent.SKIPPED: has_skipped(),
|
||||
GdUnitEvent.FAILED_COUNT: count_failures(recursive),
|
||||
GdUnitEvent.ERROR_COUNT: count_errors(recursive),
|
||||
GdUnitEvent.SKIPPED_COUNT: count_skipped(recursive)
|
||||
GdUnitEvent.FAILED: !_is_success,
|
||||
GdUnitEvent.ERRORS: _has_errors,
|
||||
GdUnitEvent.WARNINGS: _has_warnings,
|
||||
GdUnitEvent.FLAKY: _is_flaky,
|
||||
GdUnitEvent.SKIPPED: _is_skipped,
|
||||
GdUnitEvent.FAILED_COUNT: _failure_count,
|
||||
GdUnitEvent.ERROR_COUNT: _error_count,
|
||||
GdUnitEvent.SKIPPED_COUNT: _skipped_count
|
||||
}
|
||||
|
||||
|
||||
func has_failures() -> bool:
|
||||
return _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
|
||||
return c.has_failures()) or _report_collector.has_failures()
|
||||
return (
|
||||
_sub_context.any(func(c :GdUnitExecutionContext) -> bool:
|
||||
return c._has_failures if c._is_calculated else c.has_failures())
|
||||
or _report_collector.has_failures()
|
||||
)
|
||||
|
||||
|
||||
func has_errors() -> bool:
|
||||
return _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
|
||||
return c.has_errors()) or _report_collector.has_errors()
|
||||
return (
|
||||
_sub_context.any(func(c :GdUnitExecutionContext) -> bool:
|
||||
return c._has_errors if c._is_calculated else c.has_errors())
|
||||
or _report_collector.has_errors()
|
||||
)
|
||||
|
||||
|
||||
func has_warnings() -> bool:
|
||||
return _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
|
||||
return c.has_warnings()) or _report_collector.has_warnings()
|
||||
return (
|
||||
_sub_context.any(func(c :GdUnitExecutionContext) -> bool:
|
||||
return c._has_warnings if c._is_calculated else c.has_warnings())
|
||||
or _report_collector.has_warnings()
|
||||
)
|
||||
|
||||
|
||||
func has_skipped() -> bool:
|
||||
return _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
|
||||
return c.has_skipped()) or _report_collector.has_skipped()
|
||||
func is_flaky() -> bool:
|
||||
return (
|
||||
_sub_context.any(func(c :GdUnitExecutionContext) -> bool:
|
||||
return c._is_flaky if c._is_calculated else c.is_flaky())
|
||||
or _test_execution_iteration > 1
|
||||
)
|
||||
|
||||
|
||||
func count_failures(recursive :bool) -> int:
|
||||
func is_success() -> bool:
|
||||
if _sub_context.is_empty():
|
||||
return not has_failures()
|
||||
|
||||
var failed_context := _sub_context.filter(func(c :GdUnitExecutionContext) -> bool:
|
||||
return !(c._is_success if c._is_calculated else c.is_success()))
|
||||
return failed_context.is_empty() and not has_failures()
|
||||
|
||||
|
||||
func is_skipped() -> bool:
|
||||
return (
|
||||
_sub_context.any(func(c :GdUnitExecutionContext) -> bool:
|
||||
return c._is_skipped if c._is_calculated else c.is_skipped())
|
||||
or test_case.is_skipped() if test_case != null else false
|
||||
)
|
||||
|
||||
|
||||
func is_interupted() -> bool:
|
||||
return false if test_case == null else test_case.is_interupted()
|
||||
|
||||
|
||||
func count_failures(recursive: bool) -> int:
|
||||
if not recursive:
|
||||
return _report_collector.count_failures()
|
||||
return _sub_context\
|
||||
|
@ -159,7 +308,7 @@ func count_failures(recursive :bool) -> int:
|
|||
return c.count_failures(recursive)).reduce(sum, _report_collector.count_failures())
|
||||
|
||||
|
||||
func count_errors(recursive :bool) -> int:
|
||||
func count_errors(recursive: bool) -> int:
|
||||
if not recursive:
|
||||
return _report_collector.count_errors()
|
||||
return _sub_context\
|
||||
|
@ -167,7 +316,7 @@ func count_errors(recursive :bool) -> int:
|
|||
return c.count_errors(recursive)).reduce(sum, _report_collector.count_errors())
|
||||
|
||||
|
||||
func count_skipped(recursive :bool) -> int:
|
||||
func count_skipped(recursive: bool) -> int:
|
||||
if not recursive:
|
||||
return _report_collector.count_skipped()
|
||||
return _sub_context\
|
||||
|
@ -182,15 +331,24 @@ func count_orphans() -> int:
|
|||
return _orphan_monitor.orphan_nodes() - orphans
|
||||
|
||||
|
||||
func sum(accum :int, number :int) -> int:
|
||||
func sum(accum: int, number: int) -> int:
|
||||
return accum + number
|
||||
|
||||
|
||||
func register_auto_free(obj :Variant) -> Variant:
|
||||
func retry_execution() -> bool:
|
||||
var retry := _test_execution_iteration < 1 if not _flaky_test_check else _test_execution_iteration < _flaky_test_retries
|
||||
if retry:
|
||||
_test_execution_iteration += 1
|
||||
return retry
|
||||
|
||||
|
||||
func register_auto_free(obj: Variant) -> Variant:
|
||||
return _memory_observer.register_auto_free(obj)
|
||||
|
||||
|
||||
## Runs the gdunit garbage collector to free registered object
|
||||
func gc() -> void:
|
||||
# unreference last used assert form the test to prevent memory leaks
|
||||
GdUnitThreadManager.get_current_context().clear_assert()
|
||||
await _memory_observer.gc()
|
||||
orphan_monitor_stop()
|
||||
|
|
|
@ -18,6 +18,7 @@ func register_auto_free(obj :Variant) -> Variant:
|
|||
if not is_instance_valid(obj):
|
||||
return obj
|
||||
# do not register on GDScriptNativeClass
|
||||
@warning_ignore("unsafe_cast")
|
||||
if typeof(obj) == TYPE_OBJECT and (obj as Object).is_class("GDScriptNativeClass") :
|
||||
return obj
|
||||
#if obj is GDScript or obj is ScriptExtension:
|
||||
|
@ -41,6 +42,7 @@ static func _is_instance_guard_enabled() -> bool:
|
|||
return false
|
||||
|
||||
|
||||
@warning_ignore("unsafe_method_access")
|
||||
static func debug_observe(name :String, obj :Object, indent :int = 0) -> void:
|
||||
if not _show_debug:
|
||||
return
|
||||
|
@ -54,7 +56,7 @@ static func debug_observe(name :String, obj :Object, indent :int = 0) -> void:
|
|||
prints(name, obj, obj.get_class(), obj.get_name())
|
||||
|
||||
|
||||
static func guard_instance(obj :Object) -> Object:
|
||||
static func guard_instance(obj :Object) -> void:
|
||||
if not _is_instance_guard_enabled():
|
||||
return
|
||||
var tag := TAG_OBSERVE_INSTANCE + str(abs(obj.get_instance_id()))
|
||||
|
@ -62,7 +64,6 @@ static func guard_instance(obj :Object) -> Object:
|
|||
return
|
||||
debug_observe("Gard on instance", obj)
|
||||
Engine.set_meta(tag, obj)
|
||||
return obj
|
||||
|
||||
|
||||
static func unguard_instance(obj :Object, verbose := true) -> void:
|
||||
|
@ -78,7 +79,7 @@ static func unguard_instance(obj :Object, verbose := true) -> void:
|
|||
static func gc_guarded_instance(name :String, instance :Object) -> void:
|
||||
if not _is_instance_guard_enabled():
|
||||
return
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
unguard_instance(instance, false)
|
||||
if is_instance_valid(instance) and instance is RefCounted:
|
||||
# finally do this very hacky stuff
|
||||
|
@ -90,8 +91,8 @@ static func gc_guarded_instance(name :String, instance :Object) -> void:
|
|||
# if base_script:
|
||||
# base_script.unreference()
|
||||
debug_observe(name, instance)
|
||||
instance.unreference()
|
||||
await Engine.get_main_loop().process_frame
|
||||
(instance as RefCounted).unreference()
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
|
||||
|
||||
static func gc_on_guarded_instances() -> void:
|
||||
|
@ -106,7 +107,7 @@ static func gc_on_guarded_instances() -> void:
|
|||
|
||||
# store the object into global store aswell to be verified by 'is_marked_auto_free'
|
||||
func _tag_object(obj :Variant) -> void:
|
||||
var tagged_object := Engine.get_meta(TAG_AUTO_FREE, []) as Array
|
||||
var tagged_object: Array = Engine.get_meta(TAG_AUTO_FREE, [])
|
||||
tagged_object.append(obj)
|
||||
Engine.set_meta(TAG_AUTO_FREE, tagged_object)
|
||||
|
||||
|
@ -116,16 +117,18 @@ func gc() -> void:
|
|||
if _store.is_empty():
|
||||
return
|
||||
# give engine time to free objects to process objects marked by queue_free()
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
if _is_stdout_verbose:
|
||||
print_verbose("GdUnit4:gc():running", " freeing %d objects .." % _store.size())
|
||||
var tagged_objects := Engine.get_meta(TAG_AUTO_FREE, []) as Array
|
||||
var tagged_objects: Array = Engine.get_meta(TAG_AUTO_FREE, [])
|
||||
while not _store.is_empty():
|
||||
var value :Variant = _store.pop_front()
|
||||
tagged_objects.erase(value)
|
||||
await GdUnitTools.free_instance(value, _is_stdout_verbose)
|
||||
assert(_store.is_empty(), "The memory observer has still entries in the store!")
|
||||
|
||||
|
||||
## Checks whether the specified object is registered for automatic release
|
||||
static func is_marked_auto_free(obj :Object) -> bool:
|
||||
return Engine.get_meta(TAG_AUTO_FREE, []).has(obj)
|
||||
static func is_marked_auto_free(obj: Variant) -> bool:
|
||||
var tagged_objects: Array = Engine.get_meta(TAG_AUTO_FREE, [])
|
||||
return tagged_objects.has(obj)
|
||||
|
|
|
@ -3,7 +3,6 @@ class_name GdUnitTestReportCollector
|
|||
extends RefCounted
|
||||
|
||||
|
||||
var _execution_context_id :int
|
||||
var _reports :Array[GdUnitReport] = []
|
||||
|
||||
|
||||
|
@ -23,11 +22,6 @@ static func __filter_is_skipped(report :GdUnitReport) -> bool:
|
|||
return report.is_skipped()
|
||||
|
||||
|
||||
func _init(execution_context_id :int) -> void:
|
||||
_execution_context_id = execution_context_id
|
||||
GdUnitSignals.instance().gdunit_report.connect(on_reports)
|
||||
|
||||
|
||||
func count_failures() -> int:
|
||||
return _reports.filter(__filter_is_failure).size()
|
||||
|
||||
|
@ -64,7 +58,5 @@ func reports() -> Array[GdUnitReport]:
|
|||
return _reports
|
||||
|
||||
|
||||
# Consumes reports emitted by tests
|
||||
func on_reports(execution_context_id :int, report :GdUnitReport) -> void:
|
||||
if execution_context_id == _execution_context_id:
|
||||
_reports.append(report)
|
||||
func push_back(report :GdUnitReport) -> void:
|
||||
_reports.push_back(report)
|
||||
|
|
|
@ -5,7 +5,7 @@ class_name GdUnitTestSuiteExecutor
|
|||
# preload all asserts here
|
||||
@warning_ignore("unused_private_class_variable")
|
||||
var _assertions := GdUnitAssertions.new()
|
||||
var _executeStage :IGdUnitExecutionStage = GdUnitTestSuiteExecutionStage.new()
|
||||
var _executeStage := GdUnitTestSuiteExecutionStage.new()
|
||||
|
||||
|
||||
func _init(debug_mode :bool = false) -> void:
|
||||
|
@ -17,8 +17,8 @@ func execute(test_suite :GdUnitTestSuite) -> void:
|
|||
if not orphan_detection_enabled:
|
||||
prints("!!! Reporting orphan nodes is disabled. Please check GdUnit settings.")
|
||||
|
||||
Engine.get_main_loop().root.call_deferred("add_child", test_suite)
|
||||
await Engine.get_main_loop().process_frame
|
||||
(Engine.get_main_loop() as SceneTree).root.call_deferred("add_child", test_suite)
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
await _executeStage.execute(GdUnitExecutionContext.of_test_suite(test_suite))
|
||||
|
||||
|
||||
|
|
|
@ -4,86 +4,38 @@ class_name GdUnitTestCaseAfterStage
|
|||
extends IGdUnitExecutionStage
|
||||
|
||||
|
||||
var _test_name :StringName = ""
|
||||
var _call_stage :bool
|
||||
var _call_stage: bool
|
||||
|
||||
|
||||
func _init(call_stage := true) -> void:
|
||||
_call_stage = call_stage
|
||||
|
||||
|
||||
func _execute(context :GdUnitExecutionContext) -> void:
|
||||
func _execute(context: GdUnitExecutionContext) -> void:
|
||||
var test_suite := context.test_suite
|
||||
|
||||
if _call_stage:
|
||||
@warning_ignore("redundant_await")
|
||||
await test_suite.after_test()
|
||||
# unreference last used assert form the test to prevent memory leaks
|
||||
GdUnitThreadManager.get_current_context().set_assert(null)
|
||||
|
||||
await context.gc()
|
||||
await context.error_monitor_stop()
|
||||
if context.test_case.is_skipped():
|
||||
|
||||
var reports := context.build_reports()
|
||||
|
||||
if context.is_skipped():
|
||||
fire_test_skipped(context)
|
||||
else:
|
||||
fire_test_ended(context)
|
||||
if is_instance_valid(context.test_case):
|
||||
context.test_case.dispose()
|
||||
fire_event(GdUnitEvent.new() \
|
||||
.test_after(context.get_test_suite_path(),
|
||||
context.get_test_suite_name(),
|
||||
context.get_test_case_name(),
|
||||
context.get_execution_statistics(),
|
||||
reports))
|
||||
|
||||
|
||||
func set_test_name(test_name :StringName) -> void:
|
||||
_test_name = test_name
|
||||
|
||||
|
||||
func fire_test_ended(context :GdUnitExecutionContext) -> void:
|
||||
var test_suite := context.test_suite
|
||||
var test_name := context._test_case_name if _test_name.is_empty() else _test_name
|
||||
var reports := collect_reports(context)
|
||||
var orphans := collect_orphans(context, reports)
|
||||
|
||||
fire_event(GdUnitEvent.new()\
|
||||
.test_after(test_suite.get_script().resource_path, test_suite.get_name(), test_name, context.build_report_statistics(orphans), reports))
|
||||
|
||||
|
||||
func collect_orphans(context :GdUnitExecutionContext, reports :Array[GdUnitReport]) -> int:
|
||||
var orphans := 0
|
||||
if not context._sub_context.is_empty():
|
||||
orphans += add_orphan_report_test(context._sub_context[0], reports)
|
||||
orphans += add_orphan_report_teststage(context, reports)
|
||||
return orphans
|
||||
|
||||
|
||||
func collect_reports(context :GdUnitExecutionContext) -> Array[GdUnitReport]:
|
||||
var reports := context.reports()
|
||||
func fire_test_skipped(context: GdUnitExecutionContext) -> void:
|
||||
var test_case := context.test_case
|
||||
if test_case.is_interupted() and not test_case.is_expect_interupted() and test_case.report() != null:
|
||||
reports.push_back(test_case.report())
|
||||
# we combine the reports of test_before(), test_after() and test() to be reported by `fire_test_ended`
|
||||
if not context._sub_context.is_empty():
|
||||
reports.append_array(context._sub_context[0].reports())
|
||||
# needs finally to clean the test reports to avoid counting twice
|
||||
context._sub_context[0].reports().clear()
|
||||
return reports
|
||||
|
||||
|
||||
func add_orphan_report_test(context :GdUnitExecutionContext, reports :Array[GdUnitReport]) -> int:
|
||||
var orphans := context.count_orphans()
|
||||
if orphans > 0:
|
||||
reports.push_front(GdUnitReport.new()\
|
||||
.create(GdUnitReport.WARN, context.test_case.line_number(), GdAssertMessages.orphan_detected_on_test(orphans)))
|
||||
return orphans
|
||||
|
||||
|
||||
func add_orphan_report_teststage(context :GdUnitExecutionContext, reports :Array[GdUnitReport]) -> int:
|
||||
var orphans := context.count_orphans()
|
||||
if orphans > 0:
|
||||
reports.push_front(GdUnitReport.new()\
|
||||
.create(GdUnitReport.WARN, context.test_case.line_number(), GdAssertMessages.orphan_detected_on_test_setup(orphans)))
|
||||
return orphans
|
||||
|
||||
|
||||
func fire_test_skipped(context :GdUnitExecutionContext) -> void:
|
||||
var test_suite := context.test_suite
|
||||
var test_case := context.test_case
|
||||
var test_case_name := context._test_case_name if _test_name.is_empty() else _test_name
|
||||
var statistics := {
|
||||
GdUnitEvent.ORPHAN_NODES: 0,
|
||||
GdUnitEvent.ELAPSED_TIME: 0,
|
||||
|
@ -95,6 +47,11 @@ func fire_test_skipped(context :GdUnitExecutionContext) -> void:
|
|||
GdUnitEvent.SKIPPED: true,
|
||||
GdUnitEvent.SKIPPED_COUNT: 1,
|
||||
}
|
||||
var report := GdUnitReport.new().create(GdUnitReport.SKIPPED, test_case.line_number(), GdAssertMessages.test_skipped(test_case.skip_info()))
|
||||
fire_event(GdUnitEvent.new()\
|
||||
.test_after(test_suite.get_script().resource_path, test_suite.get_name(), test_case_name, statistics, [report]))
|
||||
var report := GdUnitReport.new() \
|
||||
.create(GdUnitReport.SKIPPED, test_case.line_number(), GdAssertMessages.test_skipped(test_case.skip_info()))
|
||||
fire_event(GdUnitEvent.new() \
|
||||
.test_after(context.get_test_suite_path(),
|
||||
context.get_test_suite_name(),
|
||||
context.get_test_case_name(),
|
||||
statistics,
|
||||
[report]))
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
class_name GdUnitTestCaseBeforeStage
|
||||
extends IGdUnitExecutionStage
|
||||
|
||||
|
||||
var _test_name :StringName = ""
|
||||
var _call_stage :bool
|
||||
|
||||
|
||||
|
@ -14,16 +12,10 @@ func _init(call_stage := true) -> void:
|
|||
|
||||
func _execute(context :GdUnitExecutionContext) -> void:
|
||||
var test_suite := context.test_suite
|
||||
var test_case_name := context._test_case_name if _test_name.is_empty() else _test_name
|
||||
|
||||
fire_event(GdUnitEvent.new()\
|
||||
.test_before(test_suite.get_script().resource_path, test_suite.get_name(), test_case_name))
|
||||
|
||||
.test_before(context.get_test_suite_path(), context.get_test_suite_name(), context.get_test_case_name()))
|
||||
if _call_stage:
|
||||
@warning_ignore("redundant_await")
|
||||
await test_suite.before_test()
|
||||
context.error_monitor_start()
|
||||
|
||||
|
||||
func set_test_name(test_name :StringName) -> void:
|
||||
_test_name = test_name
|
||||
|
|
|
@ -16,6 +16,9 @@ var _stage_parameterized_test :IGdUnitExecutionStage= GdUnitTestCaseParameterize
|
|||
@warning_ignore("redundant_await")
|
||||
func _execute(context :GdUnitExecutionContext) -> void:
|
||||
var test_case := context.test_case
|
||||
|
||||
context.error_monitor_start()
|
||||
|
||||
if test_case.is_parameterized():
|
||||
await _stage_parameterized_test.execute(context)
|
||||
elif test_case.is_fuzzed():
|
||||
|
@ -23,6 +26,20 @@ func _execute(context :GdUnitExecutionContext) -> void:
|
|||
else:
|
||||
await _stage_single_test.execute(context)
|
||||
|
||||
await context.gc()
|
||||
await context.error_monitor_stop()
|
||||
|
||||
# finally fire test statistics report
|
||||
fire_event(GdUnitEvent.new()\
|
||||
.test_statistics(context.get_test_suite_path(),
|
||||
context.get_test_suite_name(),
|
||||
context.get_test_case_name(),
|
||||
context.get_execution_statistics()))
|
||||
|
||||
# finally free the test instance
|
||||
if is_instance_valid(context.test_case):
|
||||
context.test_case.dispose()
|
||||
|
||||
|
||||
func set_debug_mode(debug_mode :bool = false) -> void:
|
||||
super.set_debug_mode(debug_mode)
|
||||
|
|
|
@ -12,17 +12,16 @@ func _execute(context :GdUnitExecutionContext) -> void:
|
|||
|
||||
@warning_ignore("redundant_await")
|
||||
await test_suite.after()
|
||||
# unreference last used assert form the test to prevent memory leaks
|
||||
GdUnitThreadManager.get_current_context().set_assert(null)
|
||||
await context.gc()
|
||||
|
||||
var reports := context.reports()
|
||||
var orphans := context.count_orphans()
|
||||
if orphans > 0:
|
||||
reports.push_front(GdUnitReport.new() \
|
||||
.create(GdUnitReport.WARN, 1, GdAssertMessages.orphan_detected_on_suite_setup(orphans)))
|
||||
fire_event(GdUnitEvent.new().suite_after(test_suite.get_script().resource_path, test_suite.get_name(), context.build_report_statistics(orphans, false), reports))
|
||||
var reports := context.build_reports(false)
|
||||
fire_event(GdUnitEvent.new()\
|
||||
.suite_after(context.get_test_suite_path(),\
|
||||
test_suite.get_name(),
|
||||
context.get_execution_statistics(),
|
||||
reports))
|
||||
|
||||
GdUnitFileAccess.clear_tmp()
|
||||
# Guard that checks if all doubled (spy/mock) objects are released
|
||||
GdUnitClassDoubler.check_leaked_instances()
|
||||
# we hide the scene/main window after runner is finished
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED)
|
||||
|
|
|
@ -8,7 +8,7 @@ func _execute(context :GdUnitExecutionContext) -> void:
|
|||
var test_suite := context.test_suite
|
||||
|
||||
fire_event(GdUnitEvent.new()\
|
||||
.suite_before(test_suite.get_script().resource_path, test_suite.get_name(), test_suite.get_child_count()))
|
||||
.suite_before(context.get_test_suite_path(), test_suite.get_name(), test_suite.get_child_count()))
|
||||
|
||||
@warning_ignore("redundant_await")
|
||||
await test_suite.before()
|
||||
|
|
|
@ -19,6 +19,7 @@ func _execute(context :GdUnitExecutionContext) -> void:
|
|||
if context.test_suite.__is_skipped:
|
||||
await fire_test_suite_skipped(context)
|
||||
else:
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitMemoryObserver.guard_instance(context.test_suite.__awaiter)
|
||||
await _stage_before.execute(context)
|
||||
for test_case_index in context.test_suite.get_child_count():
|
||||
|
@ -27,9 +28,9 @@ func _execute(context :GdUnitExecutionContext) -> void:
|
|||
if not is_instance_valid(test_case):
|
||||
continue
|
||||
context.test_suite.set_active_test_case(test_case.get_name())
|
||||
await _stage_test.execute(GdUnitExecutionContext.of_test_case(context, test_case.get_name()))
|
||||
await _stage_test.execute(GdUnitExecutionContext.of_test_case(context, test_case))
|
||||
# stop on first error or if fail fast is enabled
|
||||
if _fail_fast and context.test_failed():
|
||||
if _fail_fast and not context.is_success():
|
||||
break
|
||||
if test_case.is_interupted():
|
||||
# it needs to go this hard way to kill the outstanding awaits of a test case when the test timed out
|
||||
|
@ -38,14 +39,14 @@ func _execute(context :GdUnitExecutionContext) -> void:
|
|||
context.test_suite = await clone_test_suite(context.test_suite)
|
||||
await _stage_after.execute(context)
|
||||
GdUnitMemoryObserver.unguard_instance(context.test_suite.__awaiter)
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
context.test_suite.free()
|
||||
context.dispose()
|
||||
|
||||
|
||||
# clones a test suite and moves the test cases to new instance
|
||||
func clone_test_suite(test_suite :GdUnitTestSuite) -> GdUnitTestSuite:
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
dispose_timers(test_suite)
|
||||
await GdUnitMemoryObserver.gc_guarded_instance("Manually free on awaiter", test_suite.__awaiter)
|
||||
var parent := test_suite.get_parent()
|
||||
|
@ -56,10 +57,11 @@ func clone_test_suite(test_suite :GdUnitTestSuite) -> GdUnitTestSuite:
|
|||
test_suite.remove_child(child)
|
||||
_test_suite.add_child(child)
|
||||
parent.add_child(_test_suite)
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitMemoryObserver.guard_instance(_test_suite.__awaiter)
|
||||
# finally free current test suite instance
|
||||
test_suite.free()
|
||||
await Engine.get_main_loop().process_frame
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
return _test_suite
|
||||
|
||||
|
||||
|
@ -67,7 +69,7 @@ func dispose_timers(test_suite :GdUnitTestSuite) -> void:
|
|||
GdUnitTools.release_timers()
|
||||
for child in test_suite.get_children():
|
||||
if child is Timer:
|
||||
child.stop()
|
||||
(child as Timer).stop()
|
||||
test_suite.remove_child(child)
|
||||
child.free()
|
||||
|
||||
|
@ -86,7 +88,20 @@ func fire_test_suite_skipped(context :GdUnitExecutionContext) -> void:
|
|||
var test_suite := context.test_suite
|
||||
var skip_count := test_suite.get_child_count()
|
||||
fire_event(GdUnitEvent.new()\
|
||||
.suite_before(test_suite.get_script().resource_path, test_suite.get_name(), skip_count))
|
||||
.suite_before(context.get_test_suite_path(), test_suite.get_name(), skip_count))
|
||||
|
||||
|
||||
for test_case_index in context.test_suite.get_child_count():
|
||||
# iterate only over test cases
|
||||
var test_case := context.test_suite.get_child(test_case_index) as _TestCase
|
||||
if not is_instance_valid(test_case):
|
||||
continue
|
||||
var test_case_context := GdUnitExecutionContext.of_test_case(context, test_case)
|
||||
fire_event(GdUnitEvent.new()\
|
||||
.test_before(test_case_context.get_test_suite_path(), test_case_context.get_test_suite_name(), test_case_context.get_test_case_name()))
|
||||
fire_test_skipped(test_case_context)
|
||||
|
||||
|
||||
var statistics := {
|
||||
GdUnitEvent.ORPHAN_NODES: 0,
|
||||
GdUnitEvent.ELAPSED_TIME: 0,
|
||||
|
@ -99,8 +114,37 @@ func fire_test_suite_skipped(context :GdUnitExecutionContext) -> void:
|
|||
GdUnitEvent.SKIPPED: true
|
||||
}
|
||||
var report := GdUnitReport.new().create(GdUnitReport.SKIPPED, -1, GdAssertMessages.test_suite_skipped(test_suite.__skip_reason, skip_count))
|
||||
fire_event(GdUnitEvent.new().suite_after(test_suite.get_script().resource_path, test_suite.get_name(), statistics, [report]))
|
||||
await Engine.get_main_loop().process_frame
|
||||
fire_event(GdUnitEvent.new().suite_after(context.get_test_suite_path(), test_suite.get_name(), statistics, [report]))
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
|
||||
|
||||
func fire_test_skipped(context: GdUnitExecutionContext) -> void:
|
||||
var test_case := context.test_case
|
||||
var statistics := {
|
||||
GdUnitEvent.ORPHAN_NODES: 0,
|
||||
GdUnitEvent.ELAPSED_TIME: 0,
|
||||
GdUnitEvent.WARNINGS: false,
|
||||
GdUnitEvent.ERRORS: false,
|
||||
GdUnitEvent.ERROR_COUNT: 0,
|
||||
GdUnitEvent.FAILED: false,
|
||||
GdUnitEvent.FAILED_COUNT: 0,
|
||||
GdUnitEvent.SKIPPED: true,
|
||||
GdUnitEvent.SKIPPED_COUNT: 1,
|
||||
}
|
||||
var report := GdUnitReport.new() \
|
||||
.create(GdUnitReport.SKIPPED, test_case.line_number(), GdAssertMessages.test_skipped("Skipped from the entire test suite"))
|
||||
fire_event(GdUnitEvent.new() \
|
||||
.test_after(context.get_test_suite_path(),
|
||||
context.get_test_suite_name(),
|
||||
context.get_test_case_name(),
|
||||
statistics,
|
||||
[report]))
|
||||
# finally fire test statistics report
|
||||
fire_event(GdUnitEvent.new()\
|
||||
.test_statistics(context.get_test_suite_path(),
|
||||
context.get_test_suite_name(),
|
||||
context.get_test_case_name(),
|
||||
statistics))
|
||||
|
||||
|
||||
func set_debug_mode(debug_mode :bool = false) -> void:
|
||||
|
|
|
@ -14,7 +14,7 @@ var _debug_mode := false
|
|||
## await MyExecutionStage.new().execute(<GdUnitExecutionContext>)
|
||||
## [/codeblock][br]
|
||||
func execute(context :GdUnitExecutionContext) -> void:
|
||||
context.set_active()
|
||||
GdUnitThreadManager.get_current_context().set_execution_context(context)
|
||||
@warning_ignore("redundant_await")
|
||||
await _execute(context)
|
||||
|
||||
|
|
|
@ -8,10 +8,16 @@ var _stage_test :IGdUnitExecutionStage = GdUnitTestCaseFuzzedTestStage.new()
|
|||
|
||||
|
||||
func _execute(context :GdUnitExecutionContext) -> void:
|
||||
await _stage_before.execute(context)
|
||||
if not context.test_case.is_skipped():
|
||||
await _stage_test.execute(GdUnitExecutionContext.of(context))
|
||||
await _stage_after.execute(context)
|
||||
while context.retry_execution():
|
||||
var test_context := GdUnitExecutionContext.of(context)
|
||||
await _stage_before.execute(test_context)
|
||||
if not context.test_case.is_skipped():
|
||||
await _stage_test.execute(GdUnitExecutionContext.of(test_context))
|
||||
await _stage_after.execute(test_context)
|
||||
if test_context.is_success() or test_context.is_skipped() or test_context.is_interupted():
|
||||
break
|
||||
@warning_ignore("return_value_discarded")
|
||||
context.evaluate_test_retry_status()
|
||||
|
||||
|
||||
func set_debug_mode(debug_mode :bool = false) -> void:
|
||||
|
|
|
@ -15,6 +15,7 @@ func _execute(context :GdUnitExecutionContext) -> void:
|
|||
|
||||
# guard on fuzzers
|
||||
for fuzzer in fuzzers:
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitMemoryObserver.guard_instance(fuzzer)
|
||||
|
||||
for iteration in test_case.iterations():
|
||||
|
@ -46,7 +47,8 @@ func create_fuzzers(test_suite :GdUnitTestSuite, test_case :_TestCase) -> Array[
|
|||
test_case.generate_seed()
|
||||
var fuzzers :Array[Fuzzer] = []
|
||||
for fuzzer_arg in test_case.fuzzer_arguments():
|
||||
var fuzzer := _expression_runner.to_fuzzer(test_suite.get_script(), fuzzer_arg.value_as_string())
|
||||
@warning_ignore("unsafe_cast")
|
||||
var fuzzer := _expression_runner.to_fuzzer(test_suite.get_script() as GDScript, fuzzer_arg.plain_value() as String)
|
||||
fuzzer._iteration_index = 0
|
||||
fuzzer._iteration_limit = test_case.iterations()
|
||||
fuzzers.append(fuzzer)
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
class_name GdUnitTestCaseParameterSetTestStage
|
||||
extends IGdUnitExecutionStage
|
||||
|
||||
|
||||
## Executes a parameterized test case 'test_<name>()' by given parameters.[br]
|
||||
## It executes synchronized following stages[br]
|
||||
## -> test_case() [br]
|
||||
func _execute(context: GdUnitExecutionContext) -> void:
|
||||
await context.test_case.execute_paramaterized(context._test_case_parameter_set)
|
||||
await context.gc()
|
|
@ -4,6 +4,7 @@ extends IGdUnitExecutionStage
|
|||
|
||||
var _stage_before: IGdUnitExecutionStage = GdUnitTestCaseBeforeStage.new()
|
||||
var _stage_after: IGdUnitExecutionStage = GdUnitTestCaseAfterStage.new()
|
||||
var _stage_test: IGdUnitExecutionStage = GdUnitTestCaseParameterSetTestStage.new()
|
||||
|
||||
|
||||
## Executes a parameterized test case.[br]
|
||||
|
@ -12,9 +13,6 @@ var _stage_after: IGdUnitExecutionStage = GdUnitTestCaseAfterStage.new()
|
|||
func _execute(context: GdUnitExecutionContext) -> void:
|
||||
var test_case := context.test_case
|
||||
var test_parameter_index := test_case.test_parameter_index()
|
||||
var is_fail := false
|
||||
var is_error := false
|
||||
var failing_index := 0
|
||||
var parameter_set_resolver := test_case.parameter_set_resolver()
|
||||
var test_names := parameter_set_resolver.build_test_case_names(test_case)
|
||||
|
||||
|
@ -28,44 +26,55 @@ func _execute(context: GdUnitExecutionContext) -> void:
|
|||
if test_parameter_index != -1 and test_parameter_index != parameter_set_index:
|
||||
continue
|
||||
var current_test_case_name := test_names[parameter_set_index]
|
||||
_stage_before.set_test_name(current_test_case_name)
|
||||
_stage_after.set_test_name(current_test_case_name)
|
||||
var test_case_parameter_set: Array
|
||||
if parameter_set_resolver.is_parameter_set_static(parameter_set_index):
|
||||
test_case_parameter_set = parameter_sets[parameter_set_index]
|
||||
|
||||
var test_context := GdUnitExecutionContext.of(context)
|
||||
await _stage_before.execute(test_context)
|
||||
var current_parameter_set :Array
|
||||
if parameter_set_resolver.is_parameter_set_static(parameter_set_index):
|
||||
current_parameter_set = parameter_sets[parameter_set_index]
|
||||
else:
|
||||
current_parameter_set = _load_parameter_set(context, parameter_set_index)
|
||||
if not test_case.is_interupted():
|
||||
await test_case.execute_paramaterized(current_parameter_set)
|
||||
await _stage_after.execute(test_context)
|
||||
# we need to clean up the reports here so they are not reported twice
|
||||
is_fail = is_fail or test_context.count_failures(false) > 0
|
||||
is_error = is_error or test_context.count_errors(false) > 0
|
||||
failing_index = parameter_set_index - 1
|
||||
test_context.reports().clear()
|
||||
test_context._test_case_name = current_test_case_name
|
||||
var has_errors := false
|
||||
while test_context.retry_execution():
|
||||
var retry_test_context := GdUnitExecutionContext.of(test_context)
|
||||
|
||||
retry_test_context._test_case_name = current_test_case_name
|
||||
await _stage_before.execute(retry_test_context)
|
||||
if not test_case.is_interupted():
|
||||
# we need to load paramater set at execution level after the before stage to get the actual variables from the current test
|
||||
if not parameter_set_resolver.is_parameter_set_static(parameter_set_index):
|
||||
test_case_parameter_set = _load_parameter_set(context, parameter_set_index)
|
||||
await _stage_test.execute(GdUnitExecutionContext.of_parameterized_test(retry_test_context, current_test_case_name, test_case_parameter_set))
|
||||
await _stage_after.execute(retry_test_context)
|
||||
has_errors = retry_test_context.has_errors()
|
||||
if retry_test_context.is_success() or retry_test_context.is_skipped() or retry_test_context.is_interupted():
|
||||
break
|
||||
|
||||
var is_success := test_context.evaluate_test_retry_status()
|
||||
report_test_failure(context, !is_success, has_errors, parameter_set_index)
|
||||
|
||||
if test_case.is_interupted():
|
||||
break
|
||||
# add report to parent execution context if failed or an error is found
|
||||
if is_fail:
|
||||
context.reports().append(GdUnitReport.new().create(GdUnitReport.FAILURE, test_case.line_number(), "Test failed at parameterized index %d." % failing_index))
|
||||
if is_error:
|
||||
context.reports().append(GdUnitReport.new().create(GdUnitReport.ABORT, test_case.line_number(), "Test aborted at parameterized index %d." % failing_index))
|
||||
await context.gc()
|
||||
|
||||
|
||||
func report_test_failure(test_context: GdUnitExecutionContext, is_failed: bool, has_errors: bool, parameter_set_index: int) -> void:
|
||||
var test_case := test_context.test_case
|
||||
|
||||
if is_failed:
|
||||
test_context.add_report(GdUnitReport.new().create(GdUnitReport.FAILURE, test_case.line_number(), "Test failed at parameterized index %d." % parameter_set_index))
|
||||
if has_errors:
|
||||
test_context.add_report(GdUnitReport.new().create(GdUnitReport.ABORT, test_case.line_number(), "Test aborted at parameterized index %d." % parameter_set_index))
|
||||
|
||||
|
||||
func _load_parameter_set(context: GdUnitExecutionContext, parameter_set_index: int) -> Array:
|
||||
var test_case := context.test_case
|
||||
var test_suite := context.test_suite
|
||||
# we need to exchange temporary for parameter resolving the execution context
|
||||
# this is necessary because of possible usage of `auto_free` and needs to run in the parent execution context
|
||||
var save_execution_context: GdUnitExecutionContext = test_suite.__execution_context
|
||||
context.set_active()
|
||||
var thread_context := GdUnitThreadManager.get_current_context()
|
||||
var save_execution_context := thread_context.get_execution_context()
|
||||
thread_context.set_execution_context(context)
|
||||
var parameters := test_case.load_parameter_sets()
|
||||
# restore the original execution context and restart the orphan monitor to get new instances into account
|
||||
save_execution_context.set_active()
|
||||
thread_context.set_execution_context(save_execution_context)
|
||||
save_execution_context.orphan_monitor_start()
|
||||
return parameters[parameter_set_index]
|
||||
|
||||
|
@ -74,3 +83,4 @@ func set_debug_mode(debug_mode: bool=false) -> void:
|
|||
super.set_debug_mode(debug_mode)
|
||||
_stage_before.set_debug_mode(debug_mode)
|
||||
_stage_after.set_debug_mode(debug_mode)
|
||||
_stage_test.set_debug_mode(debug_mode)
|
||||
|
|
|
@ -9,10 +9,16 @@ var _stage_test :IGdUnitExecutionStage = GdUnitTestCaseSingleTestStage.new()
|
|||
|
||||
|
||||
func _execute(context :GdUnitExecutionContext) -> void:
|
||||
await _stage_before.execute(context)
|
||||
if not context.test_case.is_skipped():
|
||||
await _stage_test.execute(GdUnitExecutionContext.of(context))
|
||||
await _stage_after.execute(context)
|
||||
while context.retry_execution():
|
||||
var test_context := GdUnitExecutionContext.of(context)
|
||||
await _stage_before.execute(test_context)
|
||||
if not test_context.is_skipped():
|
||||
await _stage_test.execute(GdUnitExecutionContext.of(test_context))
|
||||
await _stage_after.execute(test_context)
|
||||
if test_context.is_success() or test_context.is_skipped() or test_context.is_interupted():
|
||||
break
|
||||
@warning_ignore("return_value_discarded")
|
||||
context.evaluate_test_retry_status()
|
||||
|
||||
|
||||
func set_debug_mode(debug_mode :bool = false) -> void:
|
||||
|
|
|
@ -3,7 +3,6 @@ extends RefCounted
|
|||
|
||||
|
||||
var _name :String
|
||||
var _parent :GdClassDescriptor = null
|
||||
var _is_inner_class :bool
|
||||
var _functions :Array[GdFunctionDescriptor]
|
||||
|
||||
|
@ -14,18 +13,10 @@ func _init(p_name :String, p_is_inner_class :bool, p_functions :Array[GdFunction
|
|||
_functions = p_functions
|
||||
|
||||
|
||||
func set_parent_clazz(p_parent :GdClassDescriptor) -> void:
|
||||
_parent = p_parent
|
||||
|
||||
|
||||
func name() -> String:
|
||||
return _name
|
||||
|
||||
|
||||
func parent() -> GdClassDescriptor:
|
||||
return _parent
|
||||
|
||||
|
||||
func is_inner_class() -> bool:
|
||||
return _is_inner_class
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ var _decoders := {
|
|||
TYPE_PACKED_COLOR_ARRAY: _on_type_Array.bind(TYPE_PACKED_COLOR_ARRAY),
|
||||
TYPE_PACKED_VECTOR2_ARRAY: _on_type_Array.bind(TYPE_PACKED_VECTOR2_ARRAY),
|
||||
TYPE_PACKED_VECTOR3_ARRAY: _on_type_Array.bind(TYPE_PACKED_VECTOR3_ARRAY),
|
||||
GdObjects.TYPE_PACKED_VECTOR4_ARRAY: _on_type_Array.bind(GdObjects.TYPE_PACKED_VECTOR4_ARRAY),
|
||||
TYPE_DICTIONARY: _on_type_Dictionary,
|
||||
TYPE_RID: _on_type_RID,
|
||||
TYPE_NODE_PATH: _on_type_NodePath,
|
||||
|
@ -63,11 +64,11 @@ func _on_type_StringName(value :StringName) -> String:
|
|||
return 'StringName("%s")' % value
|
||||
|
||||
|
||||
func _on_type_Object(value :Object, type :int) -> String:
|
||||
func _on_type_Object(value: Variant, _type: int) -> String:
|
||||
return str(value)
|
||||
|
||||
|
||||
func _on_type_Color(color :Color) -> String:
|
||||
func _on_type_Color(color: Color) -> String:
|
||||
if color == Color.BLACK:
|
||||
return "Color()"
|
||||
return "Color%s" % color
|
||||
|
@ -79,11 +80,11 @@ func _on_type_NodePath(path :NodePath) -> String:
|
|||
return 'NodePath("%s")' % path
|
||||
|
||||
|
||||
func _on_type_Callable(cb :Callable) -> String:
|
||||
func _on_type_Callable(_cb :Callable) -> String:
|
||||
return 'Callable()'
|
||||
|
||||
|
||||
func _on_type_Signal(s :Signal) -> String:
|
||||
func _on_type_Signal(_s :Signal) -> String:
|
||||
return 'Signal()'
|
||||
|
||||
|
||||
|
@ -100,7 +101,8 @@ func _on_type_Array(value :Variant, type :int) -> String:
|
|||
|
||||
TYPE_PACKED_COLOR_ARRAY:
|
||||
var colors := PackedStringArray()
|
||||
for color in value as PackedColorArray:
|
||||
for color: Color in value:
|
||||
@warning_ignore("return_value_discarded")
|
||||
colors.append(_on_type_Color(color))
|
||||
if colors.is_empty():
|
||||
return "PackedColorArray()"
|
||||
|
@ -108,7 +110,8 @@ func _on_type_Array(value :Variant, type :int) -> String:
|
|||
|
||||
TYPE_PACKED_VECTOR2_ARRAY:
|
||||
var vectors := PackedStringArray()
|
||||
for vector in value as PackedVector2Array:
|
||||
for vector: Vector2 in value:
|
||||
@warning_ignore("return_value_discarded")
|
||||
vectors.append(_on_type_Vector(vector, TYPE_VECTOR2))
|
||||
if vectors.is_empty():
|
||||
return "PackedVector2Array()"
|
||||
|
@ -116,15 +119,26 @@ func _on_type_Array(value :Variant, type :int) -> String:
|
|||
|
||||
TYPE_PACKED_VECTOR3_ARRAY:
|
||||
var vectors := PackedStringArray()
|
||||
for vector in value as PackedVector3Array:
|
||||
for vector: Vector3 in value:
|
||||
@warning_ignore("return_value_discarded")
|
||||
vectors.append(_on_type_Vector(vector, TYPE_VECTOR3))
|
||||
if vectors.is_empty():
|
||||
return "PackedVector3Array()"
|
||||
return "PackedVector3Array([%s])" % ", ".join(vectors)
|
||||
|
||||
GdObjects.TYPE_PACKED_VECTOR4_ARRAY:
|
||||
var vectors := PackedStringArray()
|
||||
for vector: Vector4 in value:
|
||||
@warning_ignore("return_value_discarded")
|
||||
vectors.append(_on_type_Vector(vector, TYPE_VECTOR4))
|
||||
if vectors.is_empty():
|
||||
return "PackedVector4Array()"
|
||||
return "PackedVector4Array([%s])" % ", ".join(vectors)
|
||||
|
||||
TYPE_PACKED_STRING_ARRAY:
|
||||
var values := PackedStringArray()
|
||||
for v in value as PackedStringArray:
|
||||
for v: String in value:
|
||||
@warning_ignore("return_value_discarded")
|
||||
values.append('"%s"' % v)
|
||||
if values.is_empty():
|
||||
return "PackedStringArray()"
|
||||
|
@ -136,7 +150,8 @@ func _on_type_Array(value :Variant, type :int) -> String:
|
|||
TYPE_PACKED_INT32_ARRAY,\
|
||||
TYPE_PACKED_INT64_ARRAY:
|
||||
var vectors := PackedStringArray()
|
||||
for vector :Variant in value as Array:
|
||||
for vector :Variant in value:
|
||||
@warning_ignore("return_value_discarded")
|
||||
vectors.append(str(vector))
|
||||
if vectors.is_empty():
|
||||
return GdObjects.type_as_string(type) + "()"
|
||||
|
@ -230,11 +245,16 @@ func _on_type_Basis(basis :Basis) -> String:
|
|||
return "Basis(Vector3%s, Vector3%s, Vector3%s)" % [basis.x, basis.y, basis.z]
|
||||
|
||||
|
||||
@warning_ignore("unsafe_cast")
|
||||
static func decode(value :Variant) -> String:
|
||||
var type := typeof(value)
|
||||
if GdArrayTools.is_type_array(type) and value.is_empty():
|
||||
if GdArrayTools.is_type_array(type) and (value as Array).is_empty():
|
||||
return "<empty>"
|
||||
var decoder :Callable = instance("GdUnitDefaultValueDecoders", func() -> GdDefaultValueDecoder: return GdDefaultValueDecoder.new()).get_decoder(type)
|
||||
var decoder :Callable = (
|
||||
instance("GdUnitDefaultValueDecoders",
|
||||
func() -> GdDefaultValueDecoder: return GdDefaultValueDecoder.new()
|
||||
) as GdDefaultValueDecoder
|
||||
).get_decoder(type)
|
||||
if decoder == null:
|
||||
push_error("No value decoder registered for type '%d'! Please open a Bug issue at 'https://github.com/MikeSchulze/gdUnit4/issues/new/choose'." % type)
|
||||
return "null"
|
||||
|
@ -243,10 +263,15 @@ static func decode(value :Variant) -> String:
|
|||
return decoder.call(value)
|
||||
|
||||
|
||||
@warning_ignore("unsafe_cast")
|
||||
static func decode_typed(type :int, value :Variant) -> String:
|
||||
if value == null:
|
||||
return "null"
|
||||
var decoder :Callable = instance("GdUnitDefaultValueDecoders", func() -> GdDefaultValueDecoder: return GdDefaultValueDecoder.new()).get_decoder(type)
|
||||
var decoder: Callable = (
|
||||
instance("GdUnitDefaultValueDecoders",
|
||||
func() -> GdDefaultValueDecoder: return GdDefaultValueDecoder.new()
|
||||
) as GdDefaultValueDecoder
|
||||
).get_decoder(type)
|
||||
if decoder == null:
|
||||
push_error("No value decoder registered for type '%d'! Please open a Bug issue at 'https://github.com/MikeSchulze/gdUnit4/issues/new/choose'." % type)
|
||||
return "null"
|
||||
|
|
|
@ -2,23 +2,39 @@ class_name GdFunctionArgument
|
|||
extends RefCounted
|
||||
|
||||
|
||||
var _cleanup_leading_spaces := RegEx.create_from_string("(?m)^[ \t]+")
|
||||
var _fix_comma_space := RegEx.create_from_string(""", {0,}\t{0,}(?=(?:[^"]*"[^"]*")*[^"]*$)(?!\\s)""")
|
||||
var _name: String
|
||||
var _type: int
|
||||
var _default_value :Variant
|
||||
var _parameter_sets :PackedStringArray = []
|
||||
|
||||
const UNDEFINED :Variant = "<-NO_ARG->"
|
||||
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
const UNDEFINED: String = "<-NO_ARG->"
|
||||
const ARG_PARAMETERIZED_TEST := "test_parameters"
|
||||
|
||||
static var _fuzzer_regex: RegEx
|
||||
static var _cleanup_leading_spaces: RegEx
|
||||
static var _fix_comma_space: RegEx
|
||||
|
||||
func _init(p_name :String, p_type :int = TYPE_MAX, value :Variant = UNDEFINED) -> void:
|
||||
var _name: String
|
||||
var _type: int
|
||||
var _type_hint: int
|
||||
var _default_value: Variant
|
||||
var _parameter_sets: PackedStringArray = []
|
||||
|
||||
|
||||
func _init(p_name: String, p_type: int, value: Variant = UNDEFINED, p_type_hint: int = TYPE_NIL) -> void:
|
||||
_init_static_variables()
|
||||
_name = p_name
|
||||
_type = p_type
|
||||
if p_name == ARG_PARAMETERIZED_TEST:
|
||||
_parameter_sets = _parse_parameter_set(value)
|
||||
_type_hint = p_type_hint
|
||||
if value != null and p_name == ARG_PARAMETERIZED_TEST:
|
||||
_parameter_sets = _parse_parameter_set(str(value))
|
||||
_default_value = value
|
||||
# is argument a fuzzer?
|
||||
if _type == TYPE_OBJECT and _fuzzer_regex.search(_name):
|
||||
_type = GdObjects.TYPE_FUZZER
|
||||
|
||||
|
||||
func _init_static_variables() -> void:
|
||||
if _fuzzer_regex == null:
|
||||
_fuzzer_regex = GdUnitTools.to_regex("((?!(fuzzer_(seed|iterations)))fuzzer?\\w+)( ?+= ?+| ?+:= ?+| ?+:Fuzzer ?+= ?+|)")
|
||||
_cleanup_leading_spaces = RegEx.create_from_string("(?m)^[ \t]+")
|
||||
_fix_comma_space = RegEx.create_from_string(""", {0,}\t{0,}(?=(?:[^"]*"[^"]*")*[^"]*$)(?!\\s)""")
|
||||
|
||||
|
||||
func name() -> String:
|
||||
|
@ -29,20 +45,58 @@ func default() -> Variant:
|
|||
return GodotVersionFixures.convert(_default_value, _type)
|
||||
|
||||
|
||||
func set_value(value: String) -> void:
|
||||
# we onle need to apply default values for Objects, all others are provided by the method descriptor
|
||||
if _type == GdObjects.TYPE_FUZZER:
|
||||
_default_value = value
|
||||
return
|
||||
if _name == ARG_PARAMETERIZED_TEST:
|
||||
_parameter_sets = _parse_parameter_set(value)
|
||||
_default_value = value
|
||||
return
|
||||
|
||||
if _type == TYPE_NIL or _type == GdObjects.TYPE_VARIANT:
|
||||
_type = _extract_value_type(value)
|
||||
_default_value = value
|
||||
if _default_value == null:
|
||||
_default_value = value
|
||||
|
||||
|
||||
func _extract_value_type(value: String) -> int:
|
||||
if value != UNDEFINED:
|
||||
if _fuzzer_regex.search(_name):
|
||||
return GdObjects.TYPE_FUZZER
|
||||
if value.rfind(")") == value.length()-1:
|
||||
return GdObjects.TYPE_FUNC
|
||||
return _type
|
||||
|
||||
|
||||
func value_as_string() -> String:
|
||||
if has_default():
|
||||
return str(_default_value)
|
||||
return GdDefaultValueDecoder.decode_typed(_type, _default_value)
|
||||
return ""
|
||||
|
||||
|
||||
func plain_value() -> Variant:
|
||||
return _default_value
|
||||
|
||||
|
||||
func type() -> int:
|
||||
return _type
|
||||
|
||||
|
||||
func type_hint() -> int:
|
||||
return _type_hint
|
||||
|
||||
|
||||
func has_default() -> bool:
|
||||
return not is_same(_default_value, UNDEFINED)
|
||||
|
||||
|
||||
func is_typed_array() -> bool:
|
||||
return _type == TYPE_ARRAY and _type_hint != TYPE_NIL
|
||||
|
||||
|
||||
func is_parameter_set() -> bool:
|
||||
return _name == ARG_PARAMETERIZED_TEST
|
||||
|
||||
|
@ -60,10 +114,12 @@ static func get_parameter_set(parameters :Array[GdFunctionArgument]) -> GdFuncti
|
|||
|
||||
func _to_string() -> String:
|
||||
var s := _name
|
||||
if _type != TYPE_MAX:
|
||||
if _type != TYPE_NIL:
|
||||
s += ":" + GdObjects.type_as_string(_type)
|
||||
if _default_value != UNDEFINED:
|
||||
s += "=" + str(_default_value)
|
||||
if _type_hint != TYPE_NIL:
|
||||
s += "[%s]" % GdObjects.type_as_string(_type_hint)
|
||||
if typeof(_default_value) != TYPE_STRING:
|
||||
s += "=" + value_as_string()
|
||||
return s
|
||||
|
||||
|
||||
|
@ -85,6 +141,7 @@ func _parse_parameter_set(input :String) -> PackedStringArray:
|
|||
for c in buf:
|
||||
current_index += 1
|
||||
matched = current_index == buf.size()
|
||||
@warning_ignore("return_value_discarded")
|
||||
collected_characters.push_back(c)
|
||||
|
||||
match c:
|
||||
|
@ -108,6 +165,7 @@ func _parse_parameter_set(input :String) -> PackedStringArray:
|
|||
if matched:
|
||||
var parameters := _fix_comma_space.sub(collected_characters.get_string_from_utf8(), ", ", true)
|
||||
if not parameters.is_empty():
|
||||
@warning_ignore("return_value_discarded")
|
||||
output.append(parameters)
|
||||
collected_characters.clear()
|
||||
matched = false
|
||||
|
|
|
@ -6,6 +6,7 @@ var _is_static :bool
|
|||
var _is_engine :bool
|
||||
var _is_coroutine :bool
|
||||
var _name :String
|
||||
var _source_path: String
|
||||
var _line_number :int
|
||||
var _return_type :int
|
||||
var _return_class :String
|
||||
|
@ -13,6 +14,18 @@ var _args : Array[GdFunctionArgument]
|
|||
var _varargs :Array[GdFunctionArgument]
|
||||
|
||||
|
||||
|
||||
static func create(p_name: String, p_source_path: String, p_source_line: int, p_return_type: int, p_args: Array[GdFunctionArgument] = []) -> GdFunctionDescriptor:
|
||||
var fd := GdFunctionDescriptor.new(p_name, p_source_line, false, false, false, p_return_type, "", p_args)
|
||||
fd.enrich_file_info(p_source_path, p_source_line)
|
||||
return fd
|
||||
|
||||
static func create_static(p_name: String, p_source_path: String, p_source_line: int, p_return_type: int, p_args: Array[GdFunctionArgument] = []) -> GdFunctionDescriptor:
|
||||
var fd := GdFunctionDescriptor.new(p_name, p_source_line, false, true, false, p_return_type, "", p_args)
|
||||
fd.enrich_file_info(p_source_path, p_source_line)
|
||||
return fd
|
||||
|
||||
|
||||
func _init(p_name :String,
|
||||
p_line_number :int,
|
||||
p_is_virtual :bool,
|
||||
|
@ -34,10 +47,19 @@ func _init(p_name :String,
|
|||
_varargs = p_varargs
|
||||
|
||||
|
||||
func with_return_class(clazz_name: String) -> GdFunctionDescriptor:
|
||||
_return_class = clazz_name
|
||||
return self
|
||||
|
||||
|
||||
func name() -> String:
|
||||
return _name
|
||||
|
||||
|
||||
func source_path() -> String:
|
||||
return _source_path
|
||||
|
||||
|
||||
func line_number() -> int:
|
||||
return _line_number
|
||||
|
||||
|
@ -74,7 +96,7 @@ func is_private() -> bool:
|
|||
return name().begins_with("_") and not is_virtual()
|
||||
|
||||
|
||||
func return_type() -> Variant:
|
||||
func return_type() -> int:
|
||||
return _return_type
|
||||
|
||||
|
||||
|
@ -84,6 +106,19 @@ func return_type_as_string() -> String:
|
|||
return GdObjects.type_as_string(return_type())
|
||||
|
||||
|
||||
@warning_ignore("unsafe_cast")
|
||||
func set_argument_value(arg_name: String, value: String) -> void:
|
||||
(
|
||||
_args.filter(func(arg: GdFunctionArgument) -> bool: return arg.name() == arg_name)\
|
||||
.front() as GdFunctionArgument
|
||||
).set_value(value)
|
||||
|
||||
|
||||
func enrich_file_info(p_source_path: String, p_line_number: int) -> void:
|
||||
_source_path = p_source_path
|
||||
_line_number = p_line_number
|
||||
|
||||
|
||||
func args() -> Array[GdFunctionArgument]:
|
||||
return _args
|
||||
|
||||
|
@ -92,34 +127,13 @@ func varargs() -> Array[GdFunctionArgument]:
|
|||
return _varargs
|
||||
|
||||
|
||||
func typeless() -> String:
|
||||
var func_signature := ""
|
||||
if _return_type == TYPE_NIL:
|
||||
func_signature = "func %s(%s) -> void:" % [name(), typeless_args()]
|
||||
elif _return_type == GdObjects.TYPE_VARIANT:
|
||||
func_signature = "func %s(%s) -> Variant:" % [name(), typeless_args()]
|
||||
else:
|
||||
func_signature = "func %s(%s) -> %s:" % [name(), typeless_args(), return_type_as_string()]
|
||||
return "static " + func_signature if is_static() else func_signature
|
||||
|
||||
|
||||
func typeless_args() -> String:
|
||||
var collect := PackedStringArray()
|
||||
for arg in args():
|
||||
if arg.has_default():
|
||||
collect.push_back( arg.name() + "=" + arg.value_as_string())
|
||||
else:
|
||||
collect.push_back(arg.name())
|
||||
for arg in varargs():
|
||||
collect.push_back(arg.name() + "=" + arg.value_as_string())
|
||||
return ", ".join(collect)
|
||||
|
||||
|
||||
func typed_args() -> String:
|
||||
var collect := PackedStringArray()
|
||||
for arg in args():
|
||||
@warning_ignore("return_value_discarded")
|
||||
collect.push_back(arg._to_string())
|
||||
for arg in varargs():
|
||||
@warning_ignore("return_value_discarded")
|
||||
collect.push_back(arg._to_string())
|
||||
return ", ".join(collect)
|
||||
|
||||
|
@ -135,28 +149,23 @@ func _to_string() -> String:
|
|||
|
||||
|
||||
# extract function description given by Object.get_method_list()
|
||||
static func extract_from(descriptor :Dictionary) -> GdFunctionDescriptor:
|
||||
var function_flags :int = descriptor["flags"]
|
||||
var is_virtual_ :bool = function_flags & METHOD_FLAG_VIRTUAL
|
||||
var is_static_ :bool = function_flags & METHOD_FLAG_STATIC
|
||||
var is_vararg_ :bool = function_flags & METHOD_FLAG_VARARG
|
||||
#var is_const :bool = function_flags & METHOD_FLAG_CONST
|
||||
#var is_core :bool = function_flags & METHOD_FLAG_OBJECT_CORE
|
||||
#var is_default :bool = function_flags & METHOD_FLAGS_DEFAULT
|
||||
#prints("is_virtual: ", is_virtual)
|
||||
#prints("is_static: ", is_static)
|
||||
#prints("is_const: ", is_const)
|
||||
#prints("is_core: ", is_core)
|
||||
#prints("is_default: ", is_default)
|
||||
#prints("is_vararg: ", is_vararg)
|
||||
static func extract_from(descriptor :Dictionary, is_engine_ := true) -> GdFunctionDescriptor:
|
||||
var func_name: String = descriptor["name"]
|
||||
var function_flags: int = descriptor["flags"]
|
||||
var return_descriptor: Dictionary = descriptor["return"]
|
||||
var clazz_name: String = return_descriptor["class_name"]
|
||||
var is_virtual_: bool = function_flags & METHOD_FLAG_VIRTUAL
|
||||
var is_static_: bool = function_flags & METHOD_FLAG_STATIC
|
||||
var is_vararg_: bool = function_flags & METHOD_FLAG_VARARG
|
||||
|
||||
return GdFunctionDescriptor.new(
|
||||
descriptor["name"],
|
||||
func_name,
|
||||
-1,
|
||||
is_virtual_,
|
||||
is_static_,
|
||||
true,
|
||||
_extract_return_type(descriptor["return"]),
|
||||
descriptor["return"]["class_name"],
|
||||
is_engine_,
|
||||
_extract_return_type(return_descriptor),
|
||||
clazz_name,
|
||||
_extract_args(descriptor),
|
||||
_build_varargs(is_vararg_)
|
||||
)
|
||||
|
@ -185,13 +194,15 @@ const enum_fix := [
|
|||
"Control.LayoutMode"]
|
||||
|
||||
|
||||
static func _extract_return_type(return_info :Dictionary) -> Variant:
|
||||
static func _extract_return_type(return_info :Dictionary) -> int:
|
||||
var type :int = return_info["type"]
|
||||
var usage :int = return_info["usage"]
|
||||
if type == TYPE_INT and usage & PROPERTY_USAGE_CLASS_IS_ENUM:
|
||||
return GdObjects.TYPE_ENUM
|
||||
if type == TYPE_NIL and usage & PROPERTY_USAGE_NIL_IS_VARIANT:
|
||||
return GdObjects.TYPE_VARIANT
|
||||
if type == TYPE_NIL and usage == 6:
|
||||
return GdObjects.TYPE_VOID
|
||||
return type
|
||||
|
||||
|
||||
|
@ -204,11 +215,10 @@ static func _extract_args(descriptor :Dictionary) -> Array[GdFunctionArgument]:
|
|||
var arg :Dictionary = arguments.pop_back()
|
||||
var arg_name := _argument_name(arg)
|
||||
var arg_type := _argument_type(arg)
|
||||
var arg_default :Variant = GdFunctionArgument.UNDEFINED
|
||||
if not defaults.is_empty():
|
||||
var default_value :Variant = defaults.pop_back()
|
||||
arg_default = GdDefaultValueDecoder.decode_typed(arg_type, default_value)
|
||||
args_.push_front(GdFunctionArgument.new(arg_name, arg_type, arg_default))
|
||||
var arg_type_hint := _argument_hint(arg)
|
||||
#var arg_class: StringName = arg["class_name"]
|
||||
var default_value: Variant = GdFunctionArgument.UNDEFINED if defaults.is_empty() else defaults.pop_back()
|
||||
args_.push_front(GdFunctionArgument.new(arg_name, arg_type, default_value, arg_type_hint))
|
||||
return args_
|
||||
|
||||
|
||||
|
@ -219,23 +229,41 @@ static func _build_varargs(p_is_vararg :bool) -> Array[GdFunctionArgument]:
|
|||
# if function has vararg we need to handle this manually by adding 10 default arguments
|
||||
var type := GdObjects.TYPE_VARARG
|
||||
for index in 10:
|
||||
varargs_.push_back(GdFunctionArgument.new("vararg%d_" % index, type, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE))
|
||||
varargs_.push_back(GdFunctionArgument.new("vararg%d_" % index, type, '"%s"' % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE))
|
||||
return varargs_
|
||||
|
||||
|
||||
static func _argument_name(arg :Dictionary) -> String:
|
||||
# add suffix to the name to prevent clash with reserved names
|
||||
return (arg["name"] + "_") as String
|
||||
return arg["name"]
|
||||
|
||||
|
||||
static func _argument_type(arg :Dictionary) -> int:
|
||||
var type :int = arg["type"]
|
||||
var usage :int = arg["usage"]
|
||||
|
||||
if type == TYPE_OBJECT:
|
||||
if arg["class_name"] == "Node":
|
||||
return GdObjects.TYPE_NODE
|
||||
if arg["class_name"] == "Fuzzer":
|
||||
return GdObjects.TYPE_FUZZER
|
||||
|
||||
# if the argument untyped we need to scan the assignef value type
|
||||
if type == TYPE_NIL and usage == PROPERTY_USAGE_NIL_IS_VARIANT:
|
||||
return GdObjects.TYPE_VARIANT
|
||||
return type
|
||||
|
||||
|
||||
static func _argument_hint(arg :Dictionary) -> int:
|
||||
var hint :int = arg["hint"]
|
||||
var hint_string :String = arg["hint_string"]
|
||||
|
||||
match hint:
|
||||
PROPERTY_HINT_ARRAY_TYPE:
|
||||
return GdObjects.string_to_type(hint_string)
|
||||
_:
|
||||
return 0
|
||||
|
||||
|
||||
static func _argument_type_as_string(arg :Dictionary) -> String:
|
||||
var type := _argument_type(arg)
|
||||
match type:
|
||||
|
|
|
@ -14,7 +14,7 @@ var TOKEN_CLASS_NAME := Token.new("class_name")
|
|||
var TOKEN_INNER_CLASS := Token.new("class")
|
||||
var TOKEN_EXTENDS := Token.new("extends")
|
||||
var TOKEN_ENUM := Token.new("enum")
|
||||
var TOKEN_FUNCTION_STATIC_DECLARATION := Token.new("staticfunc")
|
||||
var TOKEN_FUNCTION_STATIC_DECLARATION := Token.new("static func")
|
||||
var TOKEN_FUNCTION_DECLARATION := Token.new("func")
|
||||
var TOKEN_FUNCTION := Token.new(".")
|
||||
var TOKEN_FUNCTION_RETURN_TYPE := Token.new("->")
|
||||
|
@ -64,17 +64,12 @@ var TOKENS :Array[Token] = [
|
|||
OPERATOR_REMAINDER,
|
||||
]
|
||||
|
||||
var _regex_clazz_name :RegEx
|
||||
var _regex_clazz_name := GdUnitTools.to_regex("(class) ([a-zA-Z0-9_]+) (extends[a-zA-Z]+:)|(class) ([a-zA-Z0-9_]+)")
|
||||
var _regex_strip_comments := GdUnitTools.to_regex("^([^#\"']|'[^']*'|\"[^\"]*\")*\\K#.*")
|
||||
var _base_clazz :String
|
||||
var _scanned_inner_classes := PackedStringArray()
|
||||
var _script_constants := {}
|
||||
|
||||
|
||||
static func clean_up_row(row :String) -> String:
|
||||
return to_unix_format(row.replace(" ", "").replace("\t", ""))
|
||||
|
||||
|
||||
static func to_unix_format(input :String) -> String:
|
||||
return input.replace("\r\n", "\n")
|
||||
|
||||
|
@ -263,6 +258,7 @@ class TokenInnerClass extends Token:
|
|||
|
||||
func parse(source_rows :PackedStringArray, offset :int) -> void:
|
||||
# add class signature
|
||||
@warning_ignore("return_value_discarded")
|
||||
_content.append(source_rows[offset])
|
||||
# parse class content
|
||||
for row_index in range(offset+1, source_rows.size()):
|
||||
|
@ -275,8 +271,10 @@ class TokenInnerClass extends Token:
|
|||
source_row = source_row.trim_prefix("\t")
|
||||
# refomat invalid empty lines
|
||||
if source_row.dedent().is_empty():
|
||||
@warning_ignore("return_value_discarded")
|
||||
_content.append("")
|
||||
else:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_content.append(source_row)
|
||||
continue
|
||||
break
|
||||
|
@ -287,9 +285,6 @@ class TokenInnerClass extends Token:
|
|||
return "TokenInnerClass{%s}" % [_clazz_name]
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_regex_clazz_name = GdUnitTools.to_regex("(class)([a-zA-Z0-9]+)(extends[a-zA-Z]+:)|(class)([a-zA-Z0-9]+)(:)")
|
||||
|
||||
|
||||
func get_token(input :String, current_index :int) -> Token:
|
||||
for t in TOKENS:
|
||||
|
@ -352,38 +347,7 @@ func tokenize_inner_class(source_code: String, current: int, token: Token) -> To
|
|||
return TokenInnerClass.new(clazz_name)
|
||||
|
||||
|
||||
@warning_ignore("assert_always_false")
|
||||
func _process_values(left: Token, token_stack: Array, operator: Token) -> Token:
|
||||
# precheck
|
||||
if left.is_variable() and operator.is_operator():
|
||||
var lvalue :Variant = left.value()
|
||||
var value :Variant = null
|
||||
var next_token_ := token_stack.pop_front() as Token
|
||||
match operator:
|
||||
OPERATOR_ADD:
|
||||
value = lvalue + next_token_.value()
|
||||
OPERATOR_SUB:
|
||||
value = lvalue - next_token_.value()
|
||||
OPERATOR_MUL:
|
||||
value = lvalue * next_token_.value()
|
||||
OPERATOR_DIV:
|
||||
value = lvalue / next_token_.value()
|
||||
OPERATOR_REMAINDER:
|
||||
value = lvalue & next_token_.value()
|
||||
_:
|
||||
assert(false, "Unsupported operator %s" % operator)
|
||||
return Variable.new( str(value))
|
||||
return operator
|
||||
|
||||
|
||||
func parse_func_return_type(row: String) -> int:
|
||||
var token := parse_return_token(row)
|
||||
if token == TOKEN_NOT_MATCH:
|
||||
return TYPE_NIL
|
||||
return token.type()
|
||||
|
||||
|
||||
func parse_return_token(input: String) -> Token:
|
||||
func parse_return_token(input: String) -> Variable:
|
||||
var index := input.rfind(TOKEN_FUNCTION_RETURN_TYPE._token)
|
||||
if index == -1:
|
||||
return TOKEN_NOT_MATCH
|
||||
|
@ -397,10 +361,30 @@ func parse_return_token(input: String) -> Token:
|
|||
return token
|
||||
|
||||
|
||||
# Parses the argument into a argument signature
|
||||
# e.g. func foo(arg1 :int, arg2 = 20) -> [arg1, arg2]
|
||||
func parse_arguments(input: String) -> Array[GdFunctionArgument]:
|
||||
var args :Array[GdFunctionArgument] = []
|
||||
func get_function_descriptors(script: GDScript, included_functions: PackedStringArray = []) -> Array[GdFunctionDescriptor]:
|
||||
var fds: Array[GdFunctionDescriptor] = []
|
||||
for method_descriptor in script.get_script_method_list():
|
||||
var func_name: String = method_descriptor["name"]
|
||||
if included_functions.is_empty() or func_name in included_functions:
|
||||
# exclude type set/geters
|
||||
if is_getter_or_setter(func_name):
|
||||
continue
|
||||
if not fds.any(func(fd: GdFunctionDescriptor) -> bool: return fd.name() == func_name):
|
||||
fds.append(GdFunctionDescriptor.extract_from(method_descriptor, false))
|
||||
|
||||
# we need to enrich it by default arguments and line number by parsing the script
|
||||
# the engine core functions has no valid methods to get this info
|
||||
_prescan_script(script)
|
||||
_enrich_function_descriptor(script, fds)
|
||||
return fds
|
||||
|
||||
|
||||
func is_getter_or_setter(func_name: String) -> bool:
|
||||
return func_name.begins_with("@") and (func_name.ends_with("getter") or func_name.ends_with("setter"))
|
||||
|
||||
|
||||
func _parse_function_arguments(input: String) -> Dictionary:
|
||||
var arguments := {}
|
||||
var current_index := 0
|
||||
var token :Token = null
|
||||
var bracket := 0
|
||||
|
@ -431,7 +415,7 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]:
|
|||
bracket -= 1
|
||||
# if function end?
|
||||
if in_function and bracket == 0:
|
||||
return args
|
||||
return arguments
|
||||
# is function
|
||||
if token == TOKEN_FUNCTION_DECLARATION:
|
||||
token = next_token(input, current_index)
|
||||
|
@ -441,13 +425,13 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]:
|
|||
if token is FuzzerToken:
|
||||
var arg_value := _parse_end_function(input.substr(current_index), true)
|
||||
current_index += arg_value.length()
|
||||
args.append(GdFunctionArgument.new(token.name(), token.type(), arg_value))
|
||||
var arg_name :String = (token as FuzzerToken).name()
|
||||
arguments[arg_name] = arg_value.lstrip(" ")
|
||||
continue
|
||||
# is value argument
|
||||
if in_function and token.is_variable():
|
||||
var arg_name :String = token.plain_value()
|
||||
var arg_type :int = TYPE_NIL
|
||||
var arg_value :Variant = GdFunctionArgument.UNDEFINED
|
||||
var arg_name: String = (token as Variable).plain_value()
|
||||
var arg_value: String = GdFunctionArgument.UNDEFINED
|
||||
# parse type and default value
|
||||
while current_index < len(input):
|
||||
token = next_token(input, current_index)
|
||||
|
@ -460,10 +444,6 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]:
|
|||
if token == TOKEN_SPACE:
|
||||
current_index += token._consumed
|
||||
token = next_token(input, current_index)
|
||||
arg_type = GdObjects.string_as_typeof(token._token)
|
||||
# handle enum detection as argument
|
||||
if arg_type == GdObjects.TYPE_VARIANT and is_class_enum_type(token._token):
|
||||
arg_type = GdObjects.TYPE_ENUM
|
||||
TOKEN_ARGUMENT_TYPE_ASIGNMENT:
|
||||
arg_value = _parse_end_function(input.substr(current_index), true)
|
||||
current_index += arg_value.length()
|
||||
|
@ -489,28 +469,8 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]:
|
|||
TOKEN_ARGUMENT_SEPARATOR:
|
||||
if bracket <= 1:
|
||||
break
|
||||
arg_value = arg_value.lstrip(" ")
|
||||
if arg_type == TYPE_NIL and arg_value != GdFunctionArgument.UNDEFINED:
|
||||
if arg_value.begins_with("Color."):
|
||||
arg_type = TYPE_COLOR
|
||||
elif arg_value.begins_with("Vector2."):
|
||||
arg_type = TYPE_VECTOR2
|
||||
elif arg_value.begins_with("Vector3."):
|
||||
arg_type = TYPE_VECTOR3
|
||||
elif arg_value.begins_with("AABB("):
|
||||
arg_type = TYPE_AABB
|
||||
elif arg_value.begins_with("["):
|
||||
arg_type = TYPE_ARRAY
|
||||
elif arg_value.begins_with("{"):
|
||||
arg_type = TYPE_DICTIONARY
|
||||
else:
|
||||
arg_type = typeof(str_to_var(arg_value))
|
||||
if arg_value.rfind(")") == arg_value.length()-1:
|
||||
arg_type = GdObjects.TYPE_FUNC
|
||||
elif arg_type == TYPE_NIL:
|
||||
arg_type = TYPE_STRING
|
||||
args.append(GdFunctionArgument.new(arg_name, arg_type, arg_value))
|
||||
return args
|
||||
arguments[arg_name] = arg_value.lstrip(" ")
|
||||
return arguments
|
||||
|
||||
|
||||
func _parse_end_function(input: String, remove_trailing_char := false) -> String:
|
||||
|
@ -563,9 +523,10 @@ func _parse_end_function(input: String, remove_trailing_char := false) -> String
|
|||
return input.substr(0, current_index)
|
||||
|
||||
|
||||
@warning_ignore("unsafe_method_access")
|
||||
func extract_inner_class(source_rows: PackedStringArray, clazz_name :String) -> PackedStringArray:
|
||||
for row_index in source_rows.size():
|
||||
var input := GdScriptParser.clean_up_row(source_rows[row_index])
|
||||
var input := source_rows[row_index]
|
||||
var token := next_token(input, 0)
|
||||
if token.is_inner_class():
|
||||
if token.is_class_name(clazz_name):
|
||||
|
@ -574,21 +535,6 @@ func extract_inner_class(source_rows: PackedStringArray, clazz_name :String) ->
|
|||
return PackedStringArray()
|
||||
|
||||
|
||||
func extract_source_code(script_path :PackedStringArray) -> PackedStringArray:
|
||||
if script_path.is_empty():
|
||||
push_error("Invalid script path '%s'" % script_path)
|
||||
return PackedStringArray()
|
||||
#load the source code
|
||||
var resource_path := script_path[0]
|
||||
var script :GDScript = load(resource_path)
|
||||
var source_code := load_source_code(script, script_path)
|
||||
var base_script := script.get_base_script()
|
||||
if base_script:
|
||||
_base_clazz = GdObjects.extract_class_name_from_class_path([base_script.resource_path])
|
||||
source_code += load_source_code(base_script, script_path)
|
||||
return source_code
|
||||
|
||||
|
||||
func extract_func_signature(rows :PackedStringArray, index :int) -> String:
|
||||
var signature := ""
|
||||
|
||||
|
@ -604,25 +550,6 @@ func extract_func_signature(rows :PackedStringArray, index :int) -> String:
|
|||
return ""
|
||||
|
||||
|
||||
func load_source_code(script :GDScript, script_path :PackedStringArray) -> PackedStringArray:
|
||||
var map := script.get_script_constant_map()
|
||||
for key :String in map.keys():
|
||||
var value :Variant = map.get(key)
|
||||
if value is GDScript:
|
||||
var class_path := GdObjects.extract_class_path(value)
|
||||
if class_path.size() > 1:
|
||||
_scanned_inner_classes.append(class_path[1])
|
||||
|
||||
var source_code := GdScriptParser.to_unix_format(script.source_code)
|
||||
var source_rows := source_code.split("\n")
|
||||
# extract all inner class names
|
||||
# want to extract an inner class?
|
||||
if script_path.size() > 1:
|
||||
var inner_clazz := script_path[1]
|
||||
source_rows = extract_inner_class(source_rows, inner_clazz)
|
||||
return PackedStringArray(source_rows)
|
||||
|
||||
|
||||
func get_class_name(script :GDScript) -> String:
|
||||
var source_code := GdScriptParser.to_unix_format(script.source_code)
|
||||
var source_rows := source_code.split("\n")
|
||||
|
@ -635,13 +562,12 @@ func get_class_name(script :GDScript) -> String:
|
|||
token = next_token(input, current_index)
|
||||
current_index += token._consumed
|
||||
token = tokenize_value(input, current_index, token)
|
||||
return token.value()
|
||||
return (token as Variable).value()
|
||||
# if no class_name found extract from file name
|
||||
return GdObjects.to_pascal_case(script.resource_path.get_basename().get_file())
|
||||
|
||||
|
||||
func parse_func_name(row :String) -> String:
|
||||
var input := GdScriptParser.clean_up_row(row)
|
||||
func parse_func_name(input :String) -> String:
|
||||
var current_index := 0
|
||||
var token := next_token(input, current_index)
|
||||
current_index += token._consumed
|
||||
|
@ -653,100 +579,67 @@ func parse_func_name(row :String) -> String:
|
|||
return token._token
|
||||
|
||||
|
||||
func parse_functions(rows :PackedStringArray, clazz_name :String, clazz_path :PackedStringArray, included_functions := PackedStringArray()) -> Array[GdFunctionDescriptor]:
|
||||
var func_descriptors :Array[GdFunctionDescriptor] = []
|
||||
for rowIndex in rows.size():
|
||||
var row := rows[rowIndex]
|
||||
# step over inner class functions
|
||||
if row.begins_with("\t"):
|
||||
continue
|
||||
var input := GdScriptParser.clean_up_row(row)
|
||||
# skip comments and empty lines
|
||||
if input.begins_with("#") or input.length() == 0:
|
||||
continue
|
||||
var token := next_token(input, 0)
|
||||
if token == TOKEN_FUNCTION_STATIC_DECLARATION or token == TOKEN_FUNCTION_DECLARATION:
|
||||
if _is_func_included(input, included_functions):
|
||||
var func_signature := extract_func_signature(rows, rowIndex)
|
||||
var fd := parse_func_description(func_signature, clazz_name, clazz_path, rowIndex+1)
|
||||
fd._is_coroutine = is_func_coroutine(rows, rowIndex)
|
||||
func_descriptors.append(fd)
|
||||
return func_descriptors
|
||||
## Enriches the function descriptor by line number and argument default values
|
||||
## - enrich all function descriptors form current script up to all inherited scrips
|
||||
func _enrich_function_descriptor(script: GDScript, fds: Array[GdFunctionDescriptor]) -> void:
|
||||
var enriched_functions := PackedStringArray()
|
||||
var script_to_scan := script
|
||||
while script_to_scan != null:
|
||||
# do not scan the test suite base class itself
|
||||
if script_to_scan.resource_path == "res://addons/gdUnit4/src/GdUnitTestSuite.gd":
|
||||
break
|
||||
|
||||
var rows := script_to_scan.source_code.split("\n")
|
||||
for rowIndex in rows.size():
|
||||
var input := rows[rowIndex]
|
||||
# step over inner class functions
|
||||
if input.begins_with("\t"):
|
||||
continue
|
||||
# skip comments and empty lines
|
||||
if input.begins_with("#") or input.length() == 0:
|
||||
continue
|
||||
var token := next_token(input, 0)
|
||||
if token == TOKEN_FUNCTION_STATIC_DECLARATION or token == TOKEN_FUNCTION_DECLARATION:
|
||||
var function_name := parse_func_name(input)
|
||||
var fd: GdFunctionDescriptor = fds.filter(func(element: GdFunctionDescriptor) -> bool:
|
||||
# is same function name and not already enriched
|
||||
return function_name == element.name() and not enriched_functions.has(element.name())
|
||||
).pop_front()
|
||||
if fd != null:
|
||||
# add as enriched function to exclude from next iteration (could be inherited)
|
||||
@warning_ignore("return_value_discarded")
|
||||
enriched_functions.append(fd.name())
|
||||
var func_signature := extract_func_signature(rows, rowIndex)
|
||||
var func_arguments := _parse_function_arguments(func_signature)
|
||||
# enrich missing default values
|
||||
for arg_name: String in func_arguments.keys():
|
||||
var func_argument: String = func_arguments[arg_name]
|
||||
fd.set_argument_value(arg_name, func_argument)
|
||||
fd.enrich_file_info(script_to_scan.resource_path, rowIndex + 1)
|
||||
fd._is_coroutine = is_func_coroutine(rows, rowIndex)
|
||||
# enrich return class name if not set
|
||||
if fd.return_type() == TYPE_OBJECT and fd._return_class in ["", "Resource", "RefCounted"]:
|
||||
var var_token := parse_return_token(func_signature)
|
||||
if var_token != TOKEN_NOT_MATCH and var_token.type() == TYPE_OBJECT:
|
||||
fd._return_class = _patch_inner_class_names(var_token.plain_value(), "")
|
||||
# if the script ihnerits we need to scan this also
|
||||
script_to_scan = script_to_scan.get_base_script()
|
||||
|
||||
|
||||
func is_func_coroutine(rows :PackedStringArray, index :int) -> bool:
|
||||
var is_coroutine := false
|
||||
for rowIndex in range( index+1, rows.size()):
|
||||
var row := rows[rowIndex]
|
||||
is_coroutine = row.contains("await")
|
||||
for rowIndex in range(index+1, rows.size()):
|
||||
var input := rows[rowIndex]
|
||||
is_coroutine = input.contains("await")
|
||||
if is_coroutine:
|
||||
return true
|
||||
var input := GdScriptParser.clean_up_row(row)
|
||||
var token := next_token(input, 0)
|
||||
# scan until next function
|
||||
if token == TOKEN_FUNCTION_STATIC_DECLARATION or token == TOKEN_FUNCTION_DECLARATION:
|
||||
break
|
||||
return is_coroutine
|
||||
|
||||
|
||||
func _is_func_included(row :String, included_functions :PackedStringArray) -> bool:
|
||||
if included_functions.is_empty():
|
||||
return true
|
||||
for name in included_functions:
|
||||
if row.find(name) != -1:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func parse_func_description(func_signature :String, clazz_name :String, clazz_path :PackedStringArray, line_number :int) -> GdFunctionDescriptor:
|
||||
var name := parse_func_name(func_signature)
|
||||
var return_type :int
|
||||
var return_clazz := ""
|
||||
var token := parse_return_token(func_signature)
|
||||
if token == TOKEN_NOT_MATCH:
|
||||
return_type = GdObjects.TYPE_VARIANT
|
||||
else:
|
||||
return_type = token.type()
|
||||
if token.type() == TYPE_OBJECT:
|
||||
return_clazz = _patch_inner_class_names(token.value(), clazz_name)
|
||||
# is return type an enum?
|
||||
if is_class_enum_type(return_clazz):
|
||||
return_type = GdObjects.TYPE_ENUM
|
||||
|
||||
return GdFunctionDescriptor.new(
|
||||
name,
|
||||
line_number,
|
||||
is_virtual_func(clazz_name, clazz_path, name),
|
||||
is_static_func(func_signature),
|
||||
false,
|
||||
return_type,
|
||||
return_clazz,
|
||||
parse_arguments(func_signature)
|
||||
)
|
||||
|
||||
|
||||
# caches already parsed classes for virtual functions
|
||||
# key: <clazz_name> value: a Array of virtual function names
|
||||
var _virtual_func_cache := Dictionary()
|
||||
|
||||
func is_virtual_func(clazz_name :String, clazz_path :PackedStringArray, func_name :String) -> bool:
|
||||
if _virtual_func_cache.has(clazz_name):
|
||||
return _virtual_func_cache[clazz_name].has(func_name)
|
||||
var virtual_functions := Array()
|
||||
var method_list := GdObjects.extract_class_functions(clazz_name, clazz_path)
|
||||
for method_descriptor :Dictionary in method_list:
|
||||
var is_virtual_function :bool = method_descriptor["flags"] & METHOD_FLAG_VIRTUAL
|
||||
if is_virtual_function:
|
||||
virtual_functions.append(method_descriptor["name"])
|
||||
_virtual_func_cache[clazz_name] = virtual_functions
|
||||
return _virtual_func_cache[clazz_name].has(func_name)
|
||||
|
||||
|
||||
func is_static_func(func_signature :String) -> bool:
|
||||
var input := GdScriptParser.clean_up_row(func_signature)
|
||||
var token := next_token(input, 0)
|
||||
return token == TOKEN_FUNCTION_STATIC_DECLARATION
|
||||
|
||||
|
||||
func is_inner_class(clazz_path :PackedStringArray) -> bool:
|
||||
return clazz_path.size() > 1
|
||||
|
||||
|
@ -755,39 +648,24 @@ func is_func_end(row :String) -> bool:
|
|||
return row.strip_edges(false, true).ends_with(":")
|
||||
|
||||
|
||||
func is_class_enum_type(value :String) -> bool:
|
||||
if value == "Variant":
|
||||
return false
|
||||
# first check is given value a enum from the current class
|
||||
if _script_constants.has(value):
|
||||
return true
|
||||
# otherwise we need to determie it by reflection
|
||||
var script := GDScript.new()
|
||||
script.source_code = """
|
||||
extends Resource
|
||||
|
||||
static func is_class_enum_type() -> bool:
|
||||
return typeof(%s) == TYPE_DICTIONARY
|
||||
|
||||
""".dedent() % value
|
||||
script.reload()
|
||||
return script.call("is_class_enum_type")
|
||||
|
||||
|
||||
func _patch_inner_class_names(clazz :String, clazz_name :String) -> String:
|
||||
var base_clazz := clazz_name.split(".")[0]
|
||||
func _patch_inner_class_names(clazz :String, clazz_name :String = "") -> String:
|
||||
var inner_clazz_name := clazz.split(".")[0]
|
||||
if _scanned_inner_classes.has(inner_clazz_name):
|
||||
return base_clazz + "." + clazz
|
||||
return inner_clazz_name
|
||||
#var base_clazz := clazz_name.split(".")[0]
|
||||
#return base_clazz + "." + clazz
|
||||
if _script_constants.has(clazz):
|
||||
return clazz_name + "." + clazz
|
||||
return clazz
|
||||
|
||||
|
||||
func extract_functions(script :GDScript, clazz_name :String, clazz_path :PackedStringArray) -> Array[GdFunctionDescriptor]:
|
||||
var source_code := load_source_code(script, clazz_path)
|
||||
func _prescan_script(script: GDScript) -> void:
|
||||
_script_constants = script.get_script_constant_map()
|
||||
return parse_functions(source_code, clazz_name, clazz_path)
|
||||
for key :String in _script_constants.keys():
|
||||
var value :Variant = _script_constants.get(key)
|
||||
if value is GDScript:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_scanned_inner_classes.append(key)
|
||||
|
||||
|
||||
func parse(clazz_name :String, clazz_path :PackedStringArray) -> GdUnitResult:
|
||||
|
@ -795,13 +673,22 @@ func parse(clazz_name :String, clazz_path :PackedStringArray) -> GdUnitResult:
|
|||
return GdUnitResult.error("Invalid script path '%s'" % clazz_path)
|
||||
var is_inner_class_ := is_inner_class(clazz_path)
|
||||
var script :GDScript = load(clazz_path[0])
|
||||
var function_descriptors := extract_functions(script, clazz_name, clazz_path)
|
||||
_prescan_script(script)
|
||||
|
||||
if is_inner_class_:
|
||||
var inner_class_name := clazz_path[1]
|
||||
if _scanned_inner_classes.has(inner_class_name):
|
||||
# do load only on inner class source code and enrich the stored script instance
|
||||
var source_code := _load_inner_class(script, inner_class_name)
|
||||
script = _script_constants.get(inner_class_name)
|
||||
script.source_code = source_code
|
||||
var function_descriptors := get_function_descriptors(script)
|
||||
var gd_class := GdClassDescriptor.new(clazz_name, is_inner_class_, function_descriptors)
|
||||
# iterate over class dependencies
|
||||
script = script.get_base_script()
|
||||
while script != null:
|
||||
clazz_name = GdObjects.extract_class_name_from_class_path([script.resource_path])
|
||||
function_descriptors = extract_functions(script, clazz_name, clazz_path)
|
||||
gd_class.set_parent_clazz(GdClassDescriptor.new(clazz_name, is_inner_class_, function_descriptors))
|
||||
script = script.get_base_script()
|
||||
return GdUnitResult.success(gd_class)
|
||||
|
||||
|
||||
func _load_inner_class(script: GDScript, inner_clazz: String) -> String:
|
||||
var source_rows := GdScriptParser.to_unix_format(script.source_code).split("\n")
|
||||
# extract all inner class names
|
||||
var inner_class_code := extract_inner_class(source_rows, inner_clazz)
|
||||
return "\n".join(inner_class_code)
|
||||
|
|
|
@ -9,18 +9,66 @@ func __run_expression() -> Variant:
|
|||
|
||||
"""
|
||||
|
||||
func execute(src_script :GDScript, expression :String) -> Variant:
|
||||
var constructor_args_regex := RegEx.create_from_string("new\\((?<args>.*)\\)")
|
||||
|
||||
|
||||
func execute(src_script: GDScript, value: Variant) -> Variant:
|
||||
if typeof(value) != TYPE_STRING:
|
||||
return value
|
||||
|
||||
var expression: String = value
|
||||
var parameter_map := src_script.get_script_constant_map()
|
||||
for key: String in parameter_map.keys():
|
||||
var parameter_value: Variant = parameter_map[key]
|
||||
# check we need to construct from inner class
|
||||
# we need to use the original class instance from the script_constant_map otherwise we run into a runtime error
|
||||
if expression.begins_with(key + ".new") and parameter_value is GDScript:
|
||||
var object: GDScript = parameter_value
|
||||
var args := build_constructor_arguments(parameter_map, expression.substr(expression.find("new")))
|
||||
if args.is_empty():
|
||||
return object.new()
|
||||
return object.callv("new", args)
|
||||
|
||||
var script := GDScript.new()
|
||||
var resource_path := "res://addons/gdUnit4/src/Fuzzers.gd" if src_script.resource_path.is_empty() else src_script.resource_path
|
||||
script.source_code = CLASS_TEMPLATE.dedent()\
|
||||
.replace("${clazz_path}", resource_path)\
|
||||
.replace("$expression", expression)
|
||||
script.reload(false)
|
||||
var runner :Variant = script.new()
|
||||
#script.take_over_path(resource_path)
|
||||
@warning_ignore("return_value_discarded")
|
||||
script.reload(true)
|
||||
var runner: Object = script.new()
|
||||
if runner.has_method("queue_free"):
|
||||
runner.queue_free()
|
||||
(runner as Node).queue_free()
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return runner.__run_expression()
|
||||
|
||||
|
||||
func to_fuzzer(src_script :GDScript, expression :String) -> Fuzzer:
|
||||
return execute(src_script, expression) as Fuzzer
|
||||
func build_constructor_arguments(parameter_map: Dictionary, expression: String) -> Array[Variant]:
|
||||
var result := constructor_args_regex.search(expression)
|
||||
var extracted_arguments := result.get_string("args").strip_edges()
|
||||
if extracted_arguments.is_empty():
|
||||
return []
|
||||
var arguments :Array = extracted_arguments.split(",")
|
||||
return arguments.map(func(argument: String) -> Variant:
|
||||
var value := argument.strip_edges()
|
||||
|
||||
# is argument an constant value
|
||||
if parameter_map.has(value):
|
||||
return parameter_map[value]
|
||||
# is typed named value like Vector3.ONE
|
||||
for type:int in GdObjects.TYPE_AS_STRING_MAPPINGS:
|
||||
var type_as_string:String = GdObjects.TYPE_AS_STRING_MAPPINGS[type]
|
||||
if value.begins_with(type_as_string):
|
||||
return type_convert(value, type)
|
||||
# is value a string
|
||||
if value.begins_with("'") or value.begins_with('"'):
|
||||
return value.trim_prefix("'").trim_suffix("'").trim_prefix('"').trim_suffix('"')
|
||||
# fallback to default value converting
|
||||
return str_to_var(value)
|
||||
)
|
||||
|
||||
|
||||
func to_fuzzer(src_script: GDScript, expression: String) -> Fuzzer:
|
||||
@warning_ignore("unsafe_cast")
|
||||
return execute(src_script, expression) as Fuzzer
|
||||
|
|
|
@ -45,10 +45,11 @@ func validate(input_value_set: Array) -> String:
|
|||
for input_values :Variant in input_value_set:
|
||||
var parameter_set_index := input_value_set.find(input_values)
|
||||
if input_values is Array:
|
||||
var current_arg_count :int = input_values.size()
|
||||
var arr_values: Array = input_values
|
||||
var current_arg_count := arr_values.size()
|
||||
if current_arg_count != expected_arg_count:
|
||||
return "\n The parameter set at index [%d] does not match the expected input parameters!\n The test case requires [%d] input parameters, but the set contains [%d]" % [parameter_set_index, expected_arg_count, current_arg_count]
|
||||
var error := GdUnitTestParameterSetResolver.validate_parameter_types(input_arguments, input_values, parameter_set_index)
|
||||
var error := GdUnitTestParameterSetResolver.validate_parameter_types(input_arguments, arr_values, parameter_set_index)
|
||||
if not error.is_empty():
|
||||
return error
|
||||
else:
|
||||
|
@ -97,6 +98,7 @@ func build_test_case_names(test_case: _TestCase) -> PackedStringArray:
|
|||
for parameter_set_index in parameter_sets.size():
|
||||
var parameter_set := parameter_sets[parameter_set_index]
|
||||
_static_sets_by_index[parameter_set_index] = _is_static_parameter_set(parameter_set, property_names)
|
||||
@warning_ignore("return_value_discarded")
|
||||
_test_case_names_cache.append(GdUnitTestParameterSetResolver._build_test_case_name(test_case, parameter_set, parameter_set_index))
|
||||
parameter_set_index += 1
|
||||
return _test_case_names_cache
|
||||
|
@ -121,6 +123,7 @@ func _extract_test_names_by_reflection(test_case: _TestCase) -> PackedStringArra
|
|||
var parameter_sets := load_parameter_sets(test_case)
|
||||
var test_case_names: PackedStringArray = []
|
||||
for index in parameter_sets.size():
|
||||
@warning_ignore("return_value_discarded")
|
||||
test_case_names.append(GdUnitTestParameterSetResolver._build_test_case_name(test_case, str(parameter_sets[index]), index))
|
||||
return test_case_names
|
||||
|
||||
|
@ -149,10 +152,10 @@ func load_parameter_sets(test_case: _TestCase, do_validate := false) -> Array:
|
|||
if result != OK:
|
||||
push_error("Extracting test parameters failed! Script loading error: %s" % result)
|
||||
return []
|
||||
var instance :Variant = script.new()
|
||||
var instance :Object = script.new()
|
||||
GdUnitTestParameterSetResolver.copy_properties(test_case.get_parent(), instance)
|
||||
instance.queue_free()
|
||||
var parameter_sets :Variant = instance.call("__extract_test_parameters")
|
||||
(instance as Node).queue_free()
|
||||
var parameter_sets: Array = instance.call("__extract_test_parameters")
|
||||
if not do_validate:
|
||||
return parameter_sets
|
||||
# validate the parameter set
|
||||
|
@ -169,14 +172,32 @@ func load_parameter_sets(test_case: _TestCase, do_validate := false) -> Array:
|
|||
""".dedent().trim_prefix("\n") % [
|
||||
GdAssertMessages._error("Internal Error"),
|
||||
GdAssertMessages._error("Please report this issue as a bug!")]
|
||||
test_case.get_parent().__execution_context\
|
||||
.reports()\
|
||||
.append(GdUnitReport.new().create(GdUnitReport.INTERUPTED, test_case.line_number(), error))
|
||||
GdUnitThreadManager.get_current_context()\
|
||||
.get_execution_context()\
|
||||
.add_report(GdUnitReport.new().create(GdUnitReport.INTERUPTED, test_case.line_number(), error))
|
||||
test_case.skip(true, error)
|
||||
test_case._interupted = true
|
||||
@warning_ignore("return_value_discarded")
|
||||
fixure_typed_parameters(parameter_sets, _fd.args())
|
||||
return parameter_sets
|
||||
|
||||
|
||||
func fixure_typed_parameters(parameter_sets: Array, arg_descriptors: Array[GdFunctionArgument]) -> Array:
|
||||
for parameter_set_index in parameter_sets.size():
|
||||
var parameter_set: Array = parameter_sets[parameter_set_index]
|
||||
# run over all function arguments
|
||||
for parameter_index in parameter_set.size():
|
||||
var parameter :Variant = parameter_set[parameter_index]
|
||||
var arg_descriptor: GdFunctionArgument = arg_descriptors[parameter_index]
|
||||
if parameter is Array:
|
||||
var as_array: Array = parameter
|
||||
# we need to convert the untyped array to the expected typed version
|
||||
if arg_descriptor.is_typed_array():
|
||||
parameter_set[parameter_index] = Array(as_array, arg_descriptor.type_hint(), "", null)
|
||||
return parameter_sets
|
||||
|
||||
|
||||
|
||||
static func copy_properties(source: Object, dest: Object) -> void:
|
||||
for property in source.get_property_list():
|
||||
var property_name :String = property["name"]
|
||||
|
|
|
@ -86,8 +86,10 @@ static func default_CS_template() -> String:
|
|||
|
||||
|
||||
static func build_template(source_path: String) -> String:
|
||||
var clazz_name :String = GdObjects.to_pascal_case(GdObjects.extract_class_name(source_path).value() as String)
|
||||
return GdUnitSettings.get_setting(GdUnitSettings.TEMPLATE_TS_GD, default_GD_template())\
|
||||
var clazz_name :String = GdObjects.to_pascal_case(GdObjects.extract_class_name(source_path).value_as_string())
|
||||
var template: String = GdUnitSettings.get_setting(GdUnitSettings.TEMPLATE_TS_GD, default_GD_template())
|
||||
|
||||
return template\
|
||||
.replace(TAG_TEST_SUITE_CLASS, clazz_name+"Test")\
|
||||
.replace(TAG_SOURCE_RESOURCE_PATH, source_path)\
|
||||
.replace(TAG_SOURCE_CLASS_NAME, clazz_name)\
|
||||
|
|
|
@ -4,9 +4,9 @@ extends RefCounted
|
|||
var _thread :Thread
|
||||
var _thread_name :String
|
||||
var _thread_id :int
|
||||
var _assert :GdUnitAssert
|
||||
var _signal_collector :GdUnitSignalCollector
|
||||
var _execution_context :GdUnitExecutionContext
|
||||
var _asserts := []
|
||||
|
||||
|
||||
func _init(thread :Thread = null) -> void:
|
||||
|
@ -21,7 +21,7 @@ func _init(thread :Thread = null) -> void:
|
|||
|
||||
|
||||
func dispose() -> void:
|
||||
_assert = null
|
||||
clear_assert()
|
||||
if is_instance_valid(_signal_collector):
|
||||
_signal_collector.clear()
|
||||
_signal_collector = null
|
||||
|
@ -29,13 +29,17 @@ func dispose() -> void:
|
|||
_thread = null
|
||||
|
||||
|
||||
func set_assert(value :GdUnitAssert) -> GdUnitThreadContext:
|
||||
_assert = value
|
||||
return self
|
||||
func clear_assert() -> void:
|
||||
_asserts.clear()
|
||||
|
||||
|
||||
func set_assert(value :GdUnitAssert) -> void:
|
||||
if value != null:
|
||||
_asserts.append(value)
|
||||
|
||||
|
||||
func get_assert() -> GdUnitAssert:
|
||||
return _assert
|
||||
return null if _asserts.is_empty() else _asserts[-1]
|
||||
|
||||
|
||||
func set_execution_context(context :GdUnitExecutionContext) -> void:
|
||||
|
|
|
@ -36,6 +36,7 @@ func _run(name :String, cb :Callable) -> Variant:
|
|||
var save_current_thread_id := _current_thread_id
|
||||
var thread := Thread.new()
|
||||
thread.set_meta("name", name)
|
||||
@warning_ignore("return_value_discarded")
|
||||
thread.start(cb)
|
||||
_current_thread_id = thread.get_id() as int
|
||||
_register_thread(thread, _current_thread_id)
|
||||
|
@ -52,8 +53,9 @@ func _register_thread(thread :Thread, thread_id :int) -> void:
|
|||
|
||||
|
||||
func _unregister_thread(thread_id :int) -> void:
|
||||
var context := _thread_context_by_id.get(thread_id) as GdUnitThreadContext
|
||||
var context: GdUnitThreadContext = _thread_context_by_id.get(thread_id)
|
||||
if context:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_thread_context_by_id.erase(thread_id)
|
||||
context.dispose()
|
||||
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
## The helper class to allow to double Callable
|
||||
## Is just a wrapper to the original callable with the same function signature.
|
||||
##
|
||||
## Due to interface conflicts between 'Callable' and 'Object',
|
||||
## it is not possible to stub the 'call' and 'call_deferred' methods.
|
||||
##
|
||||
## The Callable interface and the Object class have overlapping method signatures,
|
||||
## which causes conflicts when attempting to stub these methods.
|
||||
## As a result, you cannot create stubs for 'call' and 'call_deferred' methods.
|
||||
|
||||
class_name CallableDoubler
|
||||
|
||||
|
||||
const doubler_script :Script = preload("res://addons/gdUnit4/src/doubler/CallableDoubler.gd")
|
||||
|
||||
var _cb: Callable
|
||||
|
||||
|
||||
func _init(cb: Callable) -> void:
|
||||
assert(cb!=null, "Invalid argument <cb> must not be null")
|
||||
_cb = cb
|
||||
|
||||
## --- helpers -----------------------------------------------------------------------------------------------------------------------------
|
||||
static func map_func_name(method_info: Dictionary) -> String:
|
||||
return method_info["name"]
|
||||
|
||||
|
||||
## We do not want to double all functions based on Object for this class
|
||||
## Is used on SpyBuilder to excluding functions to be doubled for Callable
|
||||
static func excluded_functions() -> PackedStringArray:
|
||||
return ClassDB.class_get_method_list("Object")\
|
||||
.map(CallableDoubler.map_func_name)\
|
||||
.filter(func (name: String) -> bool:
|
||||
return !CallableDoubler.callable_functions().has(name))
|
||||
|
||||
|
||||
static func non_callable_functions(name: String) -> bool:
|
||||
return ![
|
||||
# we allow "_init", is need to construct it,
|
||||
"excluded_functions",
|
||||
"non_callable_functions",
|
||||
"callable_functions",
|
||||
"map_func_name"
|
||||
].has(name)
|
||||
|
||||
|
||||
## Returns the list of supported Callable functions
|
||||
static func callable_functions() -> PackedStringArray:
|
||||
var supported_functions :Array = doubler_script.get_script_method_list()\
|
||||
.map(CallableDoubler.map_func_name)\
|
||||
.filter(CallableDoubler.non_callable_functions)
|
||||
# We manually add these functions that we cannot/may not overwrite in this class
|
||||
supported_functions.append_array(["call_deferred", "callv"])
|
||||
return supported_functions
|
||||
|
||||
|
||||
## -----------------------------------------------------------------------------------------------------------------------------------------
|
||||
## Callable functions stubing
|
||||
## -----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@warning_ignore("untyped_declaration")
|
||||
func bind(arg0=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg1=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg2=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg3=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg4=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg5=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg6=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg7=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg8=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg9=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE) -> Callable:
|
||||
# save
|
||||
var bind_values: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE)
|
||||
_cb = _cb.bindv(bind_values)
|
||||
return _cb
|
||||
|
||||
|
||||
func bindv(caller_args: Array) -> Callable:
|
||||
_cb = _cb.bindv(caller_args)
|
||||
return _cb
|
||||
|
||||
|
||||
@warning_ignore("untyped_declaration", "native_method_override", "unused_parameter")
|
||||
func call(arg0=null,
|
||||
arg1=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg2=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg3=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg4=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg5=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg6=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg7=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg8=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg9=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE) -> Variant:
|
||||
|
||||
# This is a placeholder function signanture without any functionallity!
|
||||
# It is used by the function doubler to double function signature of Callable:call()
|
||||
# The doubled function calls direct _cb.callv(<arguments>) see GdUnitSpyFunctionDoubler:TEMPLATE_CALLABLE_CALL template
|
||||
assert(false)
|
||||
return null
|
||||
|
||||
|
||||
# Is not supported, see class description
|
||||
#func call_deferred(a) -> void:
|
||||
# pass
|
||||
|
||||
|
||||
# Is not supported, see class description
|
||||
#func callv(a) -> void:
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
func get_bound_arguments() -> Array:
|
||||
return _cb.get_bound_arguments()
|
||||
|
||||
|
||||
func get_bound_arguments_count() -> int:
|
||||
return _cb.get_bound_arguments_count()
|
||||
|
||||
|
||||
func get_method() -> StringName:
|
||||
return _cb.get_method()
|
||||
|
||||
|
||||
func get_object() -> Object:
|
||||
return _cb.get_object()
|
||||
|
||||
|
||||
func get_object_id() -> int:
|
||||
return _cb.get_object_id()
|
||||
|
||||
|
||||
func hash() -> int:
|
||||
return _cb.hash()
|
||||
|
||||
|
||||
func is_custom() -> bool:
|
||||
return _cb.is_custom()
|
||||
|
||||
|
||||
func is_null() -> bool:
|
||||
return _cb.is_null()
|
||||
|
||||
|
||||
func is_standard() -> bool:
|
||||
return _cb.is_standard()
|
||||
|
||||
|
||||
func is_valid() -> bool:
|
||||
return _cb.is_valid()
|
||||
|
||||
|
||||
@warning_ignore("untyped_declaration")
|
||||
func rpc(arg0=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg1=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg2=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg3=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg4=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg5=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg6=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg7=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg8=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg9=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE) -> void:
|
||||
|
||||
var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE)
|
||||
match args.size():
|
||||
0: _cb.rpc(0)
|
||||
1: _cb.rpc(args[0])
|
||||
2: _cb.rpc(args[0], args[1])
|
||||
3: _cb.rpc(args[0], args[1], args[2])
|
||||
4: _cb.rpc(args[0], args[1], args[2], args[3])
|
||||
5: _cb.rpc(args[0], args[1], args[2], args[3], args[4])
|
||||
6: _cb.rpc(args[0], args[1], args[2], args[3], args[4], args[5])
|
||||
7: _cb.rpc(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
|
||||
8: _cb.rpc(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7])
|
||||
9: _cb.rpc(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])
|
||||
10: _cb.rpc(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9])
|
||||
|
||||
|
||||
@warning_ignore("untyped_declaration")
|
||||
func rpc_id(peer_id: int,
|
||||
arg0=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg1=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg2=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg3=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg4=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg5=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg6=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg7=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg8=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
|
||||
arg9=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE) -> void:
|
||||
|
||||
var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE)
|
||||
match args.size():
|
||||
0: _cb.rpc_id(peer_id)
|
||||
1: _cb.rpc_id(peer_id, args[0])
|
||||
2: _cb.rpc_id(peer_id, args[0], args[1])
|
||||
3: _cb.rpc_id(peer_id, args[0], args[1], args[2])
|
||||
4: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3])
|
||||
5: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4])
|
||||
6: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4], args[5])
|
||||
7: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4], args[5], args[6])
|
||||
8: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7])
|
||||
9: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])
|
||||
10: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9])
|
||||
|
||||
|
||||
func unbind(argcount: int) -> Callable:
|
||||
_cb = _cb.unbind(argcount)
|
||||
return _cb
|
|
@ -1,4 +1,5 @@
|
|||
# This class defines a value extractor by given function name and args
|
||||
class_name GdUnitFuncValueExtractor
|
||||
extends GdUnitValueExtractor
|
||||
|
||||
var _func_names :PackedStringArray
|
||||
|
@ -27,13 +28,14 @@ func args() -> Array:
|
|||
#
|
||||
# if the value not a Object or not accesible be `func_name` the value is converted to `"n.a."`
|
||||
# expecing null values
|
||||
func extract_value(value :Variant) -> Variant:
|
||||
func extract_value(value: Variant) -> Variant:
|
||||
if value == null:
|
||||
return null
|
||||
for func_name in func_names():
|
||||
if GdArrayTools.is_array_type(value):
|
||||
var values := Array()
|
||||
for element :Variant in Array(value):
|
||||
@warning_ignore("unsafe_cast")
|
||||
for element: Variant in (value as Array):
|
||||
values.append(_call_func(element, func_name))
|
||||
value = values
|
||||
else:
|
||||
|
@ -50,17 +52,19 @@ func _call_func(value :Variant, func_name :String) -> Variant:
|
|||
# for array types we need to call explicit by function name, using funcref is only supported for Objects
|
||||
# TODO extend to all array functions
|
||||
if GdArrayTools.is_array_type(value) and func_name == "empty":
|
||||
return value.is_empty()
|
||||
@warning_ignore("unsafe_cast")
|
||||
return (value as Array).is_empty()
|
||||
|
||||
if is_instance_valid(value):
|
||||
# extract from function
|
||||
if value.has_method(func_name):
|
||||
var extract := Callable(value, func_name)
|
||||
var obj_value: Object = value
|
||||
if obj_value.has_method(func_name):
|
||||
var extract := Callable(obj_value, func_name)
|
||||
if extract.is_valid():
|
||||
return value.call(func_name) if args().is_empty() else value.callv(func_name, args())
|
||||
return obj_value.call(func_name) if args().is_empty() else obj_value.callv(func_name, args())
|
||||
else:
|
||||
# if no function exists than try to extract form parmeters
|
||||
var parameter :Variant = value.get(func_name)
|
||||
var parameter: Variant = obj_value.get(func_name)
|
||||
if parameter != null:
|
||||
return parameter
|
||||
# nothing found than return 'n.a.'
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue