reinstalling GDUnit from assetlib
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 6m41s

This commit is contained in:
2026-01-26 09:05:55 +01:00
parent 4095f818f6
commit bdce8b969c
438 changed files with 22833 additions and 17 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Mike Schulze
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env -S godot -s
extends SceneTree
var _cli_runner: GdUnitTestCIRunner
func _initialize() -> void:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED)
_cli_runner = GdUnitTestCIRunner.new()
root.add_child(_cli_runner)
# 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-----------------------")
Window.print_orphan_nodes()
prints("Finallize .. done")

View File

@@ -0,0 +1 @@
uid://do2c2faoehm61

View File

@@ -0,0 +1,167 @@
#!/usr/bin/env -S godot -s
extends MainLoop
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
# gdlint: disable=max-line-length
const LOG_FRAME_TEMPLATE = """
<!DOCTYPE html>
<html style="display: inline-grid;">
<head>
<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 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
var _cmd_options := CmdOptions.new([
CmdOption.new(
"-rd, --report-directory",
"-rd <directory>",
"Specifies the output directory in which the reports are to be written. The default is res://reports/.",
TYPE_STRING,
true
)
])
var _report_root_path: String
var _current_report_path: String
var _debug_cmd_args := PackedStringArray()
func _init() -> void:
set_report_directory(GdUnitFileAccess.current_dir() + "reports")
set_current_report_path()
func _process(_delta: float) -> bool:
# check if reports exists
if not reports_available():
prints("no reports found")
return true
# only process if godot logging is enabled
if not GdUnitSettings.is_log_enabled():
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(get_cmdline_args(), true).is_error():
return true
CmdCommandHandler.new(_cmd_options).register_cb("-rd", set_report_directory)
var godot_log_file := scan_latest_godot_log()
var result := read_log_file_content(godot_log_file)
if result.is_error():
write_report(result.error_message(), godot_log_file)
return true
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, GdUnitConstants.REPORT_DIR_PREFIX
)
_current_report_path = "%s/%s%d" % [_report_root_path, GdUnitConstants.REPORT_DIR_PREFIX, iteration]
func set_report_directory(path: String) -> void:
_report_root_path = path
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):
var file_name := "%s/%s" % [path, file]
files_sorted.append(file_name)
# sort by name, the name contains the timestamp so we sort at the end by timestamp
files_sorted.sort()
return files_sorted.back()
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("", "")\
.replace(GdUnitCSIMessageWriter.CSI_BOLD, "")\
.replace(GdUnitCSIMessageWriter.CSI_ITALIC, "")\
.replace(GdUnitCSIMessageWriter.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_path := "%s/index.html" % _current_report_path
var index_file := FileAccess.open(index_path, FileAccess.READ_WRITE)
if index_file == null:
push_error(
"Can't add log path '%s' to `%s`. Error: %s"
% [godot_log_file, index_path, error_string(FileAccess.get_open_error())]
)
return
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 get_cmdline_args() -> PackedStringArray:
if _debug_cmd_args.is_empty():
return OS.get_cmdline_args()
return _debug_cmd_args

View File

@@ -0,0 +1 @@
uid://bretpek2ehht4

View File

@@ -0,0 +1,7 @@
[plugin]
name="gdUnit4"
description="Unit Testing Framework for Godot Scripts"
author="Mike Schulze"
version="6.0.3"
script="plugin.gd"

View File

@@ -0,0 +1,110 @@
@tool
extends EditorPlugin
# We need to define manually the slot id's, to be downwards compatible
const CONTEXT_SLOT_FILESYSTEM: int = 1 # EditorContextMenuPlugin.CONTEXT_SLOT_FILESYSTEM
const CONTEXT_SLOT_SCRIPT_EDITOR: int = 2 # EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR
var _gd_inspector: Control
var _gd_console: Control
var _gd_filesystem_context_menu: Variant
var _gd_scripteditor_context_menu: Variant
func _enter_tree() -> void:
var inferred_declaration: int = ProjectSettings.get_setting("debug/gdscript/warnings/inferred_declaration")
var exclude_addons: bool = ProjectSettings.get_setting("debug/gdscript/warnings/exclude_addons")
if !exclude_addons and inferred_declaration != 0:
printerr("GdUnit4: 'inferred_declaration' is set to Warning/Error!")
printerr("GdUnit4 is not 'inferred_declaration' save, you have to excluded addons (debug/gdscript/warnings/exclude_addons)")
printerr("Loading GdUnit4 Plugin failed.")
return
if check_running_in_test_env():
@warning_ignore("return_value_discarded")
GdUnitCSIMessageWriter.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 < 0x40500:
prints("This GdUnit4 plugin version '%s' requires Godot version '4.5' or higher to run." % GdUnit4Version.current())
return
GdUnitSettings.setup()
# Install the GdUnit Inspector
_gd_inspector = (load("res://addons/gdUnit4/src/ui/GdUnitInspector.tscn") as PackedScene).instantiate()
_add_context_menus()
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") as PackedScene).instantiate()
var control: Control = add_control_to_bottom_panel(_gd_console, "gdUnitConsole")
@warning_ignore("unsafe_method_access")
await _gd_console.setup_update_notification(control)
if GdUnit4CSharpApiLoader.is_api_loaded():
prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version())
else:
prints("No GdUnit4Net found.")
# Connect to be notified for script changes to be able to discover new tests
GdUnitTestDiscoverGuard.instance()
@warning_ignore("return_value_discarded")
resource_saved.connect(_on_resource_saved)
prints("Loading GdUnit4 Plugin success")
func _exit_tree() -> void:
if check_running_in_test_env():
return
if is_instance_valid(_gd_inspector):
remove_control_from_docks(_gd_inspector)
_gd_inspector.free()
_remove_context_menus()
if is_instance_valid(_gd_console):
remove_control_from_bottom_panel(_gd_console)
_gd_console.free()
var gdUnitTools: GDScript = load("res://addons/gdUnit4/src/core/GdUnitTools.gd")
@warning_ignore("unsafe_method_access")
gdUnitTools.dispose_all(true)
prints("Unload GdUnit4 Plugin success")
func check_running_in_test_env() -> bool:
var args: PackedStringArray = 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 _add_context_menus() -> void:
if Engine.get_version_info().hex >= 0x40400:
# With Godot 4.4 we have to use the 'add_context_menu_plugin' to register editor context menus
_gd_filesystem_context_menu = _preload_gdx_script("res://addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandlerV44.gdx")
call_deferred("add_context_menu_plugin", CONTEXT_SLOT_FILESYSTEM, _gd_filesystem_context_menu)
# the CONTEXT_SLOT_SCRIPT_EDITOR is adding to the script panel instead of script editor see https://github.com/godotengine/godot/pull/100556
#_gd_scripteditor_context_menu = _preload("res://addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandlerV44.gdx")
#call_deferred("add_context_menu_plugin", CONTEXT_SLOT_SCRIPT_EDITOR, _gd_scripteditor_context_menu)
# so we use the old hacky way to add the context menu
_gd_inspector.add_child(preload("res://addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd").new())
else:
# TODO Delete it if the minimum requirement for the plugin is set to Godot 4.4.
_gd_inspector.add_child(preload("res://addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd").new())
_gd_inspector.add_child(preload("res://addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd").new())
func _remove_context_menus() -> void:
if is_instance_valid(_gd_filesystem_context_menu):
call_deferred("remove_context_menu_plugin", _gd_filesystem_context_menu)
if is_instance_valid(_gd_scripteditor_context_menu):
call_deferred("remove_context_menu_plugin", _gd_scripteditor_context_menu)
func _preload_gdx_script(script_path: String) -> Variant:
var script: GDScript = GDScript.new()
script.source_code = GdUnitFileAccess.resource_as_string(script_path)
script.take_over_path(script_path)
var err :Error = script.reload()
if err != OK:
push_error("Can't create context menu %s, error: %s" % [script_path, error_string(err)])
return script.new()
func _on_resource_saved(resource: Resource) -> void:
if resource is Script:
await GdUnitTestDiscoverGuard.instance().discover(resource as Script)

View File

@@ -0,0 +1 @@
uid://bc4fimf6ynr5d

View File

@@ -0,0 +1,62 @@
@echo off
setlocal enabledelayedexpansion
:: Initialize variables
set "godot_binary="
set "filtered_args="
:: Process all arguments
set "i=0"
:parse_args
if "%~1"=="" goto end_parse_args
if "%~1"=="--godot_binary" (
set "godot_binary=%~2"
shift
shift
) else (
set "filtered_args=!filtered_args! %~1"
shift
)
goto parse_args
:end_parse_args
:: If --godot_binary wasn't provided, fallback to environment variable
if "!godot_binary!"=="" (
set "godot_binary=%GODOT_BIN%"
)
:: Check if we have a godot_binary value from any source
if "!godot_binary!"=="" (
echo Godot binary path is not specified.
echo Please either:
echo - Set the environment variable: set GODOT_BIN=C:\path\to\godot.exe
echo - Or use the --godot_binary argument: --godot_binary C:\path\to\godot.exe
exit /b 1
)
:: Check if the Godot binary exists
if not exist "!godot_binary!" (
echo Error: The specified Godot binary '!godot_binary!' does not exist.
exit /b 1
)
:: Get Godot version and check if it's a mono build
for /f "tokens=*" %%i in ('"!godot_binary!" --version') do set GODOT_VERSION=%%i
echo !GODOT_VERSION! | findstr /I "mono" >nul
if !errorlevel! equ 0 (
echo Godot .NET detected
echo Compiling c# classes ... Please Wait
dotnet build --debug
echo done !errorlevel!
)
:: Run the tests with the filtered arguments
"!godot_binary!" --path . -s -d res://addons/gdUnit4/bin/GdUnitCmdTool.gd !filtered_args!
set exit_code=%ERRORLEVEL%
echo Run tests ends with %exit_code%
:: Run the copy log command
"!godot_binary!" --headless --path . --quiet -s res://addons/gdUnit4/bin/GdUnitCopyLog.gd !filtered_args! > nul
set exit_code2=%ERRORLEVEL%
exit /b %exit_code%

View File

@@ -0,0 +1,62 @@
#!/bin/bash
# Check for command-line argument
godot_binary=""
filtered_args=""
# Process all arguments with a more compatible approach
while [ $# -gt 0 ]; do
if [ "$1" = "--godot_binary" ] && [ $# -gt 1 ]; then
# Get the next argument as the value
godot_binary="$2"
shift 2
else
# Keep non-godot_binary arguments for passing to Godot
filtered_args="$filtered_args $1"
shift
fi
done
# If --godot_binary wasn't provided, fallback to environment variable
if [ -z "$godot_binary" ]; then
godot_binary="$GODOT_BIN"
fi
# Check if we have a godot_binary value from any source
if [ -z "$godot_binary" ]; then
echo "Godot binary path is not specified."
echo "Please either:"
echo " - Set the environment variable: export GODOT_BIN=/path/to/godot"
echo " - Or use the --godot_binary argument: --godot_binary /path/to/godot"
exit 1
fi
# Check if the Godot binary exists and is executable
if [ ! -f "$godot_binary" ]; then
echo "Error: The specified Godot binary '$godot_binary' does not exist."
exit 1
fi
if [ ! -x "$godot_binary" ]; then
echo "Error: The specified Godot binary '$godot_binary' is not executable."
exit 1
fi
# Get Godot version and check if it's a .NET build
GODOT_VERSION=$("$godot_binary" --version)
if echo "$GODOT_VERSION" | grep -i "mono" > /dev/null; then
echo "Godot .NET detected"
echo "Compiling c# classes ... Please Wait"
dotnet build --debug
echo "done $?"
fi
# Run the tests with the filtered arguments
"$godot_binary" --path . -s -d res://addons/gdUnit4/bin/GdUnitCmdTool.gd $filtered_args
exit_code=$?
echo "Run tests ends with $exit_code"
# Run the copy log command
"$godot_binary" --headless --path . --quiet -s res://addons/gdUnit4/bin/GdUnitCopyLog.gd $filtered_args > /dev/null
exit_code2=$?
exit $exit_code

View File

@@ -0,0 +1,12 @@
class_name Comparator
extends Resource
enum {
EQUAL,
LESS_THAN,
LESS_EQUAL,
GREATER_THAN,
GREATER_EQUAL,
BETWEEN_EQUAL,
NOT_BETWEEN_EQUAL,
}

View File

@@ -0,0 +1 @@
uid://buiskkw1yyuw3

View File

@@ -0,0 +1,80 @@
## Factory class providing convenient static methods to create various fuzzer instances.[br]
##
## Fuzzers is a utility class that simplifies the creation of different fuzzer types
## for testing purposes. It provides static factory methods that create pre-configured
## fuzzers with sensible defaults, making it easier to set up fuzz testing in your
## test suites without manually instantiating each fuzzer type.[br]
##
## This class acts as a central access point for all fuzzer types, improving code
## readability and reducing boilerplate in test cases.[br]
##
## @tutorial(Fuzzing Testing): https://en.wikipedia.org/wiki/Fuzzing
class_name Fuzzers
extends Resource
## Generates random strings with length between [param min_length] and
## [param max_length] (inclusive), using characters from [param charset].
## See [StringFuzzer] for detailed documentation and examples.
static func rand_str(min_length: int, max_length: int, charset := StringFuzzer.DEFAULT_CHARSET) -> StringFuzzer:
return StringFuzzer.new(min_length, max_length, charset)
## Creates a [BoolFuzzer] for generating random boolean values.[br]
##
## See [BoolFuzzer] for detailed documentation and examples.
static func boolean() -> BoolFuzzer:
return BoolFuzzer.new()
## Creates an [IntFuzzer] for generating random integers within a range.[br]
##
## Generates random integers between [param from] and [param to] (inclusive)
## using [constant IntFuzzer.NORMAL] mode.
## See [IntFuzzer] for detailed documentation and examples.
static func rangei(from: int, to: int) -> IntFuzzer:
return IntFuzzer.new(from, to)
## Creates a [FloatFuzzer] for generating random floats within a range.[br]
##
## Generates random float values between [param from] and [param to] (inclusive).
## See [FloatFuzzer] for detailed documentation and examples.
static func rangef(from: float, to: float) -> FloatFuzzer:
return FloatFuzzer.new(from, to)
## Creates a [Vector2Fuzzer] for generating random 2D vectors within a range.[br]
##
## Generates random Vector2 values where each component is bounded by
## [param from] and [param to] (inclusive).
## See [Vector2Fuzzer] for detailed documentation and examples.
static func rangev2(from: Vector2, to: Vector2) -> Vector2Fuzzer:
return Vector2Fuzzer.new(from, to)
## Creates a [Vector3Fuzzer] for generating random 3D vectors within a range.[br]
##
## Generates random Vector3 values where each component is bounded by
## [param from] and [param to] (inclusive).
## See [Vector3Fuzzer] for detailed documentation and examples.
static func rangev3(from: Vector3, to: Vector3) -> Vector3Fuzzer:
return Vector3Fuzzer.new(from, to)
## Creates an [IntFuzzer] that generates only even integers.[br]
##
## Generates random even integers between [param from] and [param to] (inclusive)
## using [constant IntFuzzer.EVEN] mode.
## See [IntFuzzer] for detailed documentation about even number generation.
static func eveni(from: int, to: int) -> IntFuzzer:
return IntFuzzer.new(from, to, IntFuzzer.EVEN)
## Creates an [IntFuzzer] that generates only odd integers.[br]
##
## Generates random odd integers between [param from] and [param to] (inclusive)
## using [constant IntFuzzer.ODD] mode.
## See [IntFuzzer] for detailed documentation about odd number generation.
static func oddi(from: int, to: int) -> IntFuzzer:
return IntFuzzer.new(from, to, IntFuzzer.ODD)

View File

@@ -0,0 +1 @@
uid://drfioswpw8u2u

View File

@@ -0,0 +1,122 @@
## An Assertion Tool to verify array values
@abstract class_name GdUnitArrayAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitArrayAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitArrayAssert
## Verifies that the current Array is equal to the given one.
@abstract func is_equal(...expected: Array) -> GdUnitArrayAssert
## Verifies that the current Array is equal to the given one, ignoring case considerations.
@abstract func is_equal_ignoring_case(...expected: Array) -> GdUnitArrayAssert
## Verifies that the current Array is not equal to the given one.
@abstract func is_not_equal(...expected: Array) -> GdUnitArrayAssert
## Verifies that the current Array is not equal to the given one, ignoring case considerations.
@abstract func is_not_equal_ignoring_case(...expected: Array) -> GdUnitArrayAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitArrayAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitArrayAssert
## Verifies that the current Array is empty, it has a size of 0.
@abstract func is_empty() -> GdUnitArrayAssert
## Verifies that the current Array is not empty, it has a size of minimum 1.
@abstract func is_not_empty() -> GdUnitArrayAssert
## Verifies that the current Array is the same. [br]
## Compares the current by object reference equals
@abstract func is_same(expected: Variant) -> GdUnitArrayAssert
## Verifies that the current Array is NOT the same. [br]
## Compares the current by object reference equals
@abstract func is_not_same(expected: Variant) -> GdUnitArrayAssert
## Verifies that the current Array has a size of given value.
@abstract func has_size(expectd: int) -> GdUnitArrayAssert
## Verifies that the current Array contains the given values, in any order.[br]
## The values are compared by deep parameter comparision, for object reference compare you have to use [method contains_same]
@abstract func contains(...expected: Array) -> GdUnitArrayAssert
## Verifies that the current Array contains exactly only the given values and nothing else, in same order.[br]
## The values are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_exactly]
@abstract func contains_exactly(...expected: Array) -> GdUnitArrayAssert
## Verifies that the current Array contains exactly only the given values and nothing else, in any order.[br]
## The values are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_exactly_in_any_order]
@abstract func contains_exactly_in_any_order(...expected: Array) -> GdUnitArrayAssert
## Verifies that the current Array contains the given values, in any order.[br]
## The values are compared by object reference, for deep parameter comparision use [method contains]
@abstract func contains_same(...expected: Array) -> GdUnitArrayAssert
## Verifies that the current Array contains exactly only the given values and nothing else, in same order.[br]
## The values are compared by object reference, for deep parameter comparision use [method contains_exactly]
@abstract func contains_same_exactly(...expected: Array) -> GdUnitArrayAssert
## Verifies that the current Array contains exactly only the given values and nothing else, in any order.[br]
## The values are compared by object reference, for deep parameter comparision use [method contains_exactly_in_any_order]
@abstract func contains_same_exactly_in_any_order(...expected: Array) -> GdUnitArrayAssert
## Verifies that the current Array do NOT contains the given values, in any order.[br]
## The values are compared by deep parameter comparision, for object reference compare you have to use [method not_contains_same]
## [b]Example:[/b]
## [codeblock]
## # will succeed
## assert_array([1, 2, 3, 4, 5]).not_contains(6)
## # will fail
## assert_array([1, 2, 3, 4, 5]).not_contains(2, 6)
## [/codeblock]
@abstract func not_contains(...expected: Array) -> GdUnitArrayAssert
## Verifies that the current Array do NOT contains the given values, in any order.[br]
## The values are compared by object reference, for deep parameter comparision use [method not_contains]
## [b]Example:[/b]
## [codeblock]
## # will succeed
## assert_array([1, 2, 3, 4, 5]).not_contains(6)
## # will fail
## assert_array([1, 2, 3, 4, 5]).not_contains(2, 6)
## [/codeblock]
@abstract func not_contains_same(...expected: Array) -> GdUnitArrayAssert
## Extracts all values by given function name and optional arguments into a new ArrayAssert.
## If the elements not accessible by `func_name` the value is converted to `"n.a"`, expecting null values
@abstract func extract(func_name: String, ...func_args: Array) -> GdUnitArrayAssert
## Extracts all values by given extractor's into a new ArrayAssert.
## If the elements not extractable than the value is converted to `"n.a"`, expecting null values
## -- The argument type is Array[GdUnitValueExtractor]
@abstract func extractv(...extractors: Array) -> GdUnitArrayAssert

View File

@@ -0,0 +1 @@
uid://byeulsiqvaugq

View File

@@ -0,0 +1,47 @@
## Base interface of all GdUnit asserts
@abstract class_name GdUnitAssert
extends RefCounted
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitAssert
## Verifies that the current value is equal to expected one.
@abstract func is_equal(expected: Variant) -> GdUnitAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitAssert
## Overrides the default failure message by given custom message.[br]
## This function allows you to replace the automatically generated failure message with a more specific
## or user-friendly message that better describes the test failure context.[br]
## Usage:
## [codeblock]
## # Override with custom context-specific message
## func test_player_inventory():
## assert_that(player.get_item_count("sword"))\
## .override_failure_message("Player should have exactly one sword")\
## .is_equal(1)
## [/codeblock]
@abstract func override_failure_message(message: String) -> GdUnitAssert
## Appends a custom message to the failure message.[br]
## This can be used to add additional information to the generated failure message
## while keeping the original assertion details for better debugging context.[br]
## Usage:
## [codeblock]
## # Add context to existing failure message
## func test_player_health():
## assert_that(player.health)\
## .append_failure_message("Player was damaged by: %s" % last_damage_source)\
## .is_greater(0)
## [/codeblock]
@abstract func append_failure_message(message: String) -> GdUnitAssert

View File

@@ -0,0 +1 @@
uid://bmy2nu4w22wia

View File

@@ -0,0 +1,72 @@
class_name GdUnitAwaiter
extends RefCounted
# 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
# signal_name: signal name
# args: the expected signal arguments as an array
# timeout: the timeout in ms, default is set to 2000ms
func await_signal_on(source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> Variant:
# fail fast if the given source instance invalid
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() 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
# Waits for a specified signal sent from the <source> between idle frames and aborts with an error after the specified timeout has elapsed
# source: the object from which the signal is emitted
# signal_name: signal name
# args: the expected signal arguments as an array
# timeout: the timeout in ms, default is set to 2000ms
func await_signal_idle_frames(source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> Variant:
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()
var awaiter := GdUnitSignalAwaiter.new(timeout_millis, true)
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
# Waits for for a given amount of milliseconds
# example:
# # waits for 100ms
# await GdUnitAwaiter.await_millis(myNode, 100).completed
# use this waiter and not `await get_tree().create_timer().timeout to prevent errors when a test case is timed out
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() as SceneTree).root.add_child(timer)
timer.add_to_group("GdUnitTimers")
timer.set_one_shot(true)
timer.start(milliSec / 1000.0)
await timer.timeout
timer.queue_free()
# Waits until the next idle frame
func await_idle_frame() -> void:
await (Engine.get_main_loop() as SceneTree).process_frame

View File

@@ -0,0 +1 @@
uid://c1jp2le4lldby

View File

@@ -0,0 +1,35 @@
## An Assertion Tool to verify boolean values
@abstract class_name GdUnitBoolAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitBoolAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitBoolAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitBoolAssert
## Verifies that the current value is not equal to the given one.
@abstract func is_not_equal(expected: Variant) -> GdUnitBoolAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitBoolAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitBoolAssert
## Verifies that the current value is true.
@abstract func is_true() -> GdUnitBoolAssert
## Verifies that the current value is false.
@abstract func is_false() -> GdUnitBoolAssert

View File

@@ -0,0 +1 @@
uid://bftfpffmfb1il

View File

@@ -0,0 +1,10 @@
class_name GdUnitConstants
extends RefCounted
const NO_ARG :Variant = "<--null-->"
const EXPECT_ASSERT_REPORT_FAILURES := "expect_assert_report_failures"
## The maximum number of report history files to store
const DEFAULT_REPORT_HISTORY_COUNT = 20
const REPORT_DIR_PREFIX = "report_"

View File

@@ -0,0 +1 @@
uid://dkap7kpfh2bhg

View File

@@ -0,0 +1,79 @@
## An Assertion Tool to verify dictionary
@abstract class_name GdUnitDictionaryAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitDictionaryAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitDictionaryAssert
## Verifies that the current dictionary is equal to the given one, ignoring order.
@abstract func is_equal(expected: Variant) -> GdUnitDictionaryAssert
## Verifies that the current dictionary is not equal to the given one, ignoring order.
@abstract func is_not_equal(expected: Variant) -> GdUnitDictionaryAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitDictionaryAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitDictionaryAssert
## Verifies that the current dictionary is empty, it has a size of 0.
@abstract func is_empty() -> GdUnitDictionaryAssert
## Verifies that the current dictionary is not empty, it has a size of minimum 1.
@abstract func is_not_empty() -> GdUnitDictionaryAssert
## Verifies that the current dictionary is the same. [br]
## Compares the current by object reference equals
@abstract func is_same(expected: Variant) -> GdUnitDictionaryAssert
## Verifies that the current dictionary is NOT the same. [br]
## Compares the current by object reference equals
@abstract func is_not_same(expected: Variant) -> GdUnitDictionaryAssert
## Verifies that the current dictionary has a size of given value.
@abstract func has_size(expected: int) -> GdUnitDictionaryAssert
## Verifies that the current dictionary contains the given key(s).[br]
## The keys are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_keys]
@abstract func contains_keys(...expected: Array) -> GdUnitDictionaryAssert
## Verifies that the current dictionary contains the given key and value.[br]
## The key and value are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_key_value]
@abstract func contains_key_value(key: Variant, value: Variant) -> GdUnitDictionaryAssert
## Verifies that the current dictionary not contains the given key(s).[br]
## The keys are compared by deep parameter comparision, for object reference compare you have to use [method not_contains_same_keys]
@abstract func not_contains_keys(...expected: Array) -> GdUnitDictionaryAssert
## Verifies that the current dictionary contains the given key(s).[br]
## The keys are compared by object reference, for deep parameter comparision use [method contains_keys]
@abstract func contains_same_keys(expected: Array) -> GdUnitDictionaryAssert
## Verifies that the current dictionary contains the given key and value.[br]
## The key and value are compared by object reference, for deep parameter comparision use [method contains_key_value]
@abstract func contains_same_key_value(key: Variant, value: Variant) -> GdUnitDictionaryAssert
## Verifies that the current dictionary not contains the given key(s).
## The keys are compared by object reference, for deep parameter comparision use [method not_contains_keys]
@abstract func not_contains_same_keys(...expected: Array) -> GdUnitDictionaryAssert

View File

@@ -0,0 +1 @@
uid://8s1lymhdvlpu

View File

@@ -0,0 +1,52 @@
## An assertion tool to verify GDUnit asserts.
## This assert is for internal use only, to verify that failed asserts work as expected.
@abstract class_name GdUnitFailureAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitFailureAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitFailureAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitFailureAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitFailureAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitFailureAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitFailureAssert
## Verifies if the executed assert was successful
@abstract func is_success() -> GdUnitFailureAssert
## Verifies if the executed assert has failed
@abstract func is_failed() -> GdUnitFailureAssert
## Verifies the failure line is equal to expected one.
@abstract func has_line(expected: int) -> GdUnitFailureAssert
## Verifies the failure message is equal to expected one.
@abstract func has_message(expected: String) -> GdUnitFailureAssert
## Verifies that the failure message starts with the expected message.
@abstract func starts_with_message(expected: String) -> GdUnitFailureAssert
## Verifies that the failure message contains the expected message.
@abstract func contains_message(expected: String) -> GdUnitFailureAssert

View File

@@ -0,0 +1 @@
uid://x54vf4fue301

View File

@@ -0,0 +1,38 @@
@abstract class_name GdUnitFileAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitFileAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitFileAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitFileAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitFileAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitFileAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitFileAssert
@abstract func is_file() -> GdUnitFileAssert
@abstract func exists() -> GdUnitFileAssert
@abstract func is_script() -> GdUnitFileAssert
@abstract func contains_exactly(expected_rows :Array) -> GdUnitFileAssert

View File

@@ -0,0 +1 @@
uid://vt1hx0i6pg4h

View File

@@ -0,0 +1,75 @@
## An Assertion Tool to verify float values
@abstract class_name GdUnitFloatAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitFloatAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitFloatAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitFloatAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitFloatAssert
## Verifies that the current and expected value are approximately equal.
@abstract func is_equal_approx(expected: float, approx: float) -> GdUnitFloatAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitFloatAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitFloatAssert
## Verifies that the current value is less than the given one.
@abstract func is_less(expected: float) -> GdUnitFloatAssert
## Verifies that the current value is less than or equal the given one.
@abstract func is_less_equal(expected: float) -> GdUnitFloatAssert
## Verifies that the current value is greater than the given one.
@abstract func is_greater(expected: float) -> GdUnitFloatAssert
## Verifies that the current value is greater than or equal the given one.
@abstract func is_greater_equal(expected: float) -> GdUnitFloatAssert
## Verifies that the current value is negative.
@abstract func is_negative() -> GdUnitFloatAssert
## Verifies that the current value is not negative.
@abstract func is_not_negative() -> GdUnitFloatAssert
## Verifies that the current value is equal to zero.
@abstract func is_zero() -> GdUnitFloatAssert
## Verifies that the current value is not equal to zero.
@abstract func is_not_zero() -> GdUnitFloatAssert
## Verifies that the current value is in the given set of values.
@abstract func is_in(expected: Array) -> GdUnitFloatAssert
## Verifies that the current value is not in the given set of values.
@abstract func is_not_in(expected: Array) -> GdUnitFloatAssert
## Verifies that the current value is between the given boundaries (inclusive).
@abstract func is_between(from: float, to: float) -> GdUnitFloatAssert

View File

@@ -0,0 +1 @@
uid://l487wamffax1

View File

@@ -0,0 +1,42 @@
## An Assertion Tool to verify function callback values
@abstract class_name GdUnitFuncAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitFuncAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitFuncAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitFuncAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitFuncAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitFuncAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitFuncAssert
## Verifies that the current value is true.
@abstract func is_true() -> GdUnitFuncAssert
## Verifies that the current value is false.
@abstract func is_false() -> GdUnitFuncAssert
## Sets the timeout in ms to wait the function returnd the expected value, if the time over a failure is emitted.[br]
## e.g.[br]
## do wait until 5s the function `is_state` is returns 10 [br]
## [code]assert_func(instance, "is_state").wait_until(5000).is_equal(10)[/code]
@abstract func wait_until(timeout: int) -> GdUnitFuncAssert

View File

@@ -0,0 +1 @@
uid://bvvptcdhi1g14

View File

@@ -0,0 +1,59 @@
## An assertion tool to verify for Godot runtime errors like assert() and push notifications like push_error().
@abstract class_name GdUnitGodotErrorAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitGodotErrorAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitGodotErrorAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitGodotErrorAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitGodotErrorAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitGodotErrorAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitGodotErrorAssert
## Verifies if the executed code runs without any runtime errors
## Usage:
## [codeblock]
## await assert_error(<callable>).is_success()
## [/codeblock]
@abstract func is_success() -> GdUnitGodotErrorAssert
## Verifies if the executed code runs into a runtime error
## Usage:
## [codeblock]
## await assert_error(<callable>).is_runtime_error(<expected error message>)
## [/codeblock]
@abstract func is_runtime_error(expected_error: Variant) -> GdUnitGodotErrorAssert
## Verifies if the executed code has a push_warning() used
## Usage:
## [codeblock]
## await assert_error(<callable>).is_push_warning(<expected push warning message>)
## [/codeblock]
@abstract func is_push_warning(expected_warning: Variant) -> GdUnitGodotErrorAssert
## Verifies if the executed code has a push_error() used
## Usage:
## [codeblock]
## await assert_error(<callable>).is_push_error(<expected push error message>)
## [/codeblock]
@abstract func is_push_error(expected_error: Variant) -> GdUnitGodotErrorAssert

View File

@@ -0,0 +1 @@
uid://bwkv3a1hhdt88

View File

@@ -0,0 +1,79 @@
## An Assertion Tool to verify integer values
@abstract class_name GdUnitIntAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitIntAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitIntAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitIntAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitIntAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitIntAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitIntAssert
## Verifies that the current value is less than the given one.
@abstract func is_less(expected: int) -> GdUnitIntAssert
## Verifies that the current value is less than or equal the given one.
@abstract func is_less_equal(expected: int) -> GdUnitIntAssert
## Verifies that the current value is greater than the given one.
@abstract func is_greater(expected: int) -> GdUnitIntAssert
## Verifies that the current value is greater than or equal the given one.
@abstract func is_greater_equal(expected: int) -> GdUnitIntAssert
## Verifies that the current value is even.
@abstract func is_even() -> GdUnitIntAssert
## Verifies that the current value is odd.
@abstract func is_odd() -> GdUnitIntAssert
## Verifies that the current value is negative.
@abstract func is_negative() -> GdUnitIntAssert
## Verifies that the current value is not negative.
@abstract func is_not_negative() -> GdUnitIntAssert
## Verifies that the current value is equal to zero.
@abstract func is_zero() -> GdUnitIntAssert
## Verifies that the current value is not equal to zero.
@abstract func is_not_zero() -> GdUnitIntAssert
## Verifies that the current value is in the given set of values.
@abstract func is_in(expected: Array) -> GdUnitIntAssert
## Verifies that the current value is not in the given set of values.
@abstract func is_not_in(expected: Array) -> GdUnitIntAssert
## Verifies that the current value is between the given boundaries (inclusive).
@abstract func is_between(from: int, to: int) -> GdUnitIntAssert

View File

@@ -0,0 +1 @@
uid://ghuy35olsym1

View File

@@ -0,0 +1,51 @@
## An Assertion Tool to verify Object values
@abstract class_name GdUnitObjectAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitObjectAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitObjectAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitObjectAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitObjectAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitObjectAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitObjectAssert
## Verifies that the current object is the same as the given one.
@abstract func is_same(expected: Variant) -> GdUnitObjectAssert
## Verifies that the current object is not the same as the given one.
@abstract func is_not_same(expected: Variant) -> GdUnitObjectAssert
## Verifies that the current object is an instance of the given type.
@abstract func is_instanceof(type: Variant) -> GdUnitObjectAssert
## Verifies that the current object is not an instance of the given type.
@abstract func is_not_instanceof(type: Variant) -> GdUnitObjectAssert
## Checks whether the current object inherits from the specified type.
@abstract func is_inheriting(type: Variant) -> GdUnitObjectAssert
## Checks whether the current object does NOT inherit from the specified type.
@abstract func is_not_inheriting(type: Variant) -> GdUnitObjectAssert

View File

@@ -0,0 +1 @@
uid://dmunl8xg53sym

View File

@@ -0,0 +1,51 @@
## An Assertion Tool to verify Results
@abstract class_name GdUnitResultAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitResultAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitResultAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitResultAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitResultAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitResultAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitResultAssert
## Verifies that the result is ends up with empty
@abstract func is_empty() -> GdUnitResultAssert
## Verifies that the result is ends up with success
@abstract func is_success() -> GdUnitResultAssert
## Verifies that the result is ends up with warning
@abstract func is_warning() -> GdUnitResultAssert
## Verifies that the result is ends up with error
@abstract func is_error() -> GdUnitResultAssert
## Verifies that the result contains the given message
@abstract func contains_message(expected: String) -> GdUnitResultAssert
## Verifies that the result contains the given value
@abstract func is_value(expected: Variant) -> GdUnitResultAssert

View File

@@ -0,0 +1 @@
uid://b4n45twg8y2ar

View File

@@ -0,0 +1,325 @@
## 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.
@abstract class_name GdUnitSceneRunner
extends RefCounted
## Simulates that an action has been pressed.[br]
## [member action] : the action e.g. [code]"ui_up"[/code][br]
## [member event_index] : [url=https://docs.godotengine.org/en/4.4/classes/class_inputeventaction.html#class-inputeventaction-property-event-index]default=-1[/url][br]
@abstract func simulate_action_pressed(action: String, event_index := -1) -> GdUnitSceneRunner
## Simulates that an action is pressed.[br]
## [member action] : the action e.g. [code]"ui_up"[/code][br]
## [member event_index] : [url=https://docs.godotengine.org/en/4.4/classes/class_inputeventaction.html#class-inputeventaction-property-event-index]default=-1[/url][br]
@abstract func simulate_action_press(action: String, event_index := -1) -> GdUnitSceneRunner
## Simulates that an action has been released.[br]
## [member action] : the action e.g. [code]"ui_up"[/code][br]
## [member event_index] : [url=https://docs.godotengine.org/en/4.4/classes/class_inputeventaction.html#class-inputeventaction-property-event-index]default=-1[/url][br]
@abstract func simulate_action_release(action: String, event_index := -1) -> GdUnitSceneRunner
## 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]
@abstract func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner
## 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]
@abstract func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner
## 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]
@abstract func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner
## 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
@abstract func set_mouse_position(position: Vector2) -> GdUnitSceneRunner
## Returns the mouse's position in this Viewport using the coordinate system of this Viewport.
@abstract func get_mouse_position() -> Vector2
## Gets the current global mouse position of the current window
@abstract func get_global_mouse_position() -> Vector2
## Simulates a mouse moved to final position.[br]
## [member position] : The final mouse position
@abstract func simulate_mouse_move(position: Vector2) -> GdUnitSceneRunner
## Simulates a mouse move to the relative coordinates (offset).[br]
## [color=yellow]You must use [b]await[/b] to wait until the simulated mouse movement is complete.[/color][br]
## [br]
## [member relative] : The relative position, indicating the mouse position offset.[br]
## [member time] : The time to move the mouse by 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_move_mouse():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
## await runner.simulate_mouse_move_relative(Vector2(100,100))
## [/codeblock]
@abstract func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner
## Simulates a mouse move to the absolute coordinates.[br]
## [color=yellow]You must use [b]await[/b] to wait until the simulated mouse movement is complete.[/color][br]
## [br]
## [member position] : The final position of the mouse.[br]
## [member time] : The time to move the mouse 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_move_mouse():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
## await runner.simulate_mouse_move_absolute(Vector2(100,100))
## [/codeblock]
@abstract func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner
## Simulates a mouse button pressed.[br]
## [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
@abstract func simulate_mouse_button_pressed(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner
## Simulates a mouse button press (holding)[br]
## [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
@abstract func simulate_mouse_button_press(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner
## Simulates a mouse button released.[br]
## [member button_index] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
@abstract func simulate_mouse_button_release(button_index: MouseButton) -> GdUnitSceneRunner
## 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.
@abstract func simulate_screen_touch_pressed(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner
## 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.
@abstract func simulate_screen_touch_press(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner
## 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.
@abstract func simulate_screen_touch_release(index: int, double_tap := false) -> GdUnitSceneRunner
## 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]
@abstract func simulate_screen_touch_drag_relative(index: int, relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner
## 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]
@abstract func simulate_screen_touch_drag_absolute(index: int, position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner
## 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]
@abstract 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
## 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]
@abstract func simulate_screen_touch_drag(index: int, position: Vector2) -> GdUnitSceneRunner
## 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]
@abstract func get_screen_touch_drag_position(index: int) -> Vector2
## 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.
## [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]
@abstract func set_time_factor(time_factor: float = 1.0) -> GdUnitSceneRunner
## Simulates scene processing for a certain number of frames.[br]
## [member frames] : amount of frames to process[br]
## [member delta_milli] : the time delta between a frame in milliseconds
@abstract func simulate_frames(frames: int, delta_milli: int = -1) -> GdUnitSceneRunner
## Simulates scene processing until the given signal is emitted by the scene.[br]
## [member signal_name] : the signal to stop the simulation[br]
## [member args] : optional signal arguments to be matched for stop[br]
@abstract func simulate_until_signal(signal_name: String, ...args: Array) -> GdUnitSceneRunner
## Simulates scene processing until the given signal is emitted by the given object.[br]
## [member source] : the object that should emit the signal[br]
## [member signal_name] : the signal to stop the simulation[br]
## [member args] : optional signal arguments to be matched for stop
@abstract func simulate_until_object_signal(source: Object, signal_name: String, ...args: Array) -> GdUnitSceneRunner
## 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]
@abstract func await_input_processed() -> void
## 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]
@abstract func await_func(func_name: String, ...args: Array) -> GdUnitFuncAssert
## 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]
@abstract func await_func_on(source: Object, func_name: String, ...args: Array) -> GdUnitFuncAssert
## 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
@abstract func await_signal(signal_name: String, args := [], timeout := 2000 ) -> void
## 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] : 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
@abstract func await_signal_on(source: Object, signal_name: String, args := [], timeout := 2000 ) -> void
## 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.
@abstract func move_window_to_foreground() -> GdUnitSceneRunner
## Minimizes the scene window to a windowed mode and brings it to the background.[br]
## This ensures that the scene is hidden during testing.
@abstract func move_window_to_background() -> GdUnitSceneRunner
## Return the current value of the property with the name <name>.[br]
## [member name] : name of property[br]
## [member return] : the value of the property
@abstract func get_property(name: String) -> Variant
## Set the value <value> of the property with the name <name>.[br]
## [member name] : name of property[br]
## [member value] : value of property[br]
## [member return] : true|false depending on valid property name.
@abstract func set_property(name: String, value: Variant) -> bool
## executes the function specified by <name> in the scene and returns the result.[br]
## [member name] : the name of the function to execute[br]
## [member args] : optional function arguments[br]
## [member return] : the function result
@abstract func invoke(name: String, ...args: Array) -> Variant
## Searches for the specified node with the name in the current scene and returns it, otherwise null.[br]
## [member name] : the name of the node to find[br]
## [member recursive] : enables/disables seraching recursive[br]
## [member return] : the node if find otherwise null
@abstract func find_child(name: String, recursive: bool = true, owned: bool = false) -> Node
## Access to current running scene
@abstract func scene() -> Node

View File

@@ -0,0 +1 @@
uid://dn20c5e8kb3q3

View File

@@ -0,0 +1,46 @@
## An Assertion Tool to verify for emitted signals until a waiting time
@abstract class_name GdUnitSignalAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitSignalAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitSignalAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitSignalAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitSignalAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitSignalAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitSignalAssert
## Verifies that given signal is emitted until waiting time
@abstract func is_emitted(name: String, args := []) -> GdUnitSignalAssert
## Verifies that given signal is NOT emitted until waiting time
@abstract func is_not_emitted(name: String, args := []) -> GdUnitSignalAssert
## Verifies the signal exists checked the emitter
@abstract func is_signal_exists(name: String) -> GdUnitSignalAssert
## Sets the assert signal timeout in ms, if the time over a failure is reported.[br]
## e.g.[br]
## do wait until 5s the instance has emitted the signal `signal_a`[br]
## [code]assert_signal(instance).wait_until(5000).is_emitted("signal_a")[/code]
@abstract func wait_until(timeout: int) -> GdUnitSignalAssert

View File

@@ -0,0 +1 @@
uid://572nse6u4l86

View File

@@ -0,0 +1,71 @@
## An Assertion Tool to verify String values
@abstract class_name GdUnitStringAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitStringAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitStringAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitStringAssert
## Verifies that the current String is equal to the given one, ignoring case considerations.
@abstract func is_equal_ignoring_case(expected: Variant) -> GdUnitStringAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitStringAssert
## Verifies that the current String is not equal to the given one, ignoring case considerations.
@abstract func is_not_equal_ignoring_case(expected: Variant) -> GdUnitStringAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitStringAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitStringAssert
## Verifies that the current String is empty, it has a length of 0.
@abstract func is_empty() -> GdUnitStringAssert
## Verifies that the current String is not empty, it has a length of minimum 1.
@abstract func is_not_empty() -> GdUnitStringAssert
## Verifies that the current String contains the given String.
@abstract func contains(expected: String) -> GdUnitStringAssert
## Verifies that the current String does not contain the given String.
@abstract func not_contains(expected: String) -> GdUnitStringAssert
## Verifies that the current String does not contain the given String, ignoring case considerations.
@abstract func contains_ignoring_case(expected: String) -> GdUnitStringAssert
## Verifies that the current String does not contain the given String, ignoring case considerations.
@abstract func not_contains_ignoring_case(expected: String) -> GdUnitStringAssert
## Verifies that the current String starts with the given prefix.
@abstract func starts_with(expected: String) -> GdUnitStringAssert
## Verifies that the current String ends with the given suffix.
@abstract func ends_with(expected: String) -> GdUnitStringAssert
## Verifies that the current String has the expected length by used comparator.
@abstract func has_length(length: int, comparator: int = Comparator.EQUAL) -> GdUnitStringAssert

View File

@@ -0,0 +1 @@
uid://ip241g801xri

View File

@@ -0,0 +1 @@
uid://cgbfa4cflb5nl

View File

@@ -0,0 +1,28 @@
## A tuple implementation to hold two or many values
class_name GdUnitTuple
extends RefCounted
const NO_ARG :Variant = GdUnitConstants.NO_ARG
var __values :Array = Array()
func _init(arg0:Variant,
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) -> void:
__values = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
func values() -> Array:
return __values
func _to_string() -> String:
return "tuple(%s)" % str(__values)

View File

@@ -0,0 +1 @@
uid://mjqw2uww51fk

View File

@@ -0,0 +1,9 @@
## This is the base interface for value extraction
class_name GdUnitValueExtractor
extends RefCounted
## Extracts a value by given implementation
func extract_value(value :Variant) -> Variant:
push_error("Uninplemented func 'extract_value'")
return value

View File

@@ -0,0 +1 @@
uid://2dylh01qtb66

View File

@@ -0,0 +1,55 @@
## An Assertion Tool to verify Vector values
@abstract class_name GdUnitVectorAssert
extends GdUnitAssert
## Verifies that the current value is null.
@abstract func is_null() -> GdUnitVectorAssert
## Verifies that the current value is not null.
@abstract func is_not_null() -> GdUnitVectorAssert
## Verifies that the current value is equal to the given one.
@abstract func is_equal(expected: Variant) -> GdUnitVectorAssert
## Verifies that the current value is not equal to expected one.
@abstract func is_not_equal(expected: Variant) -> GdUnitVectorAssert
## Verifies that the current and expected value are approximately equal.
@abstract func is_equal_approx(expected: Variant, approx: Variant) -> GdUnitVectorAssert
## Overrides the default failure message by given custom message.
@abstract func override_failure_message(message: String) -> GdUnitVectorAssert
## Appends a custom message to the failure message.
@abstract func append_failure_message(message: String) -> GdUnitVectorAssert
## Verifies that the current value is less than the given one.
@abstract func is_less(expected: Variant) -> GdUnitVectorAssert
## Verifies that the current value is less than or equal the given one.
@abstract func is_less_equal(expected: Variant) -> GdUnitVectorAssert
## Verifies that the current value is greater than the given one.
@abstract func is_greater(expected: Variant) -> GdUnitVectorAssert
## Verifies that the current value is greater than or equal the given one.
@abstract func is_greater_equal(expected: Variant) -> GdUnitVectorAssert
## Verifies that the current value is between the given boundaries (inclusive).
@abstract func is_between(from: Variant, to: Variant) -> GdUnitVectorAssert
## Verifies that the current value is not between the given boundaries (inclusive).
@abstract func is_not_between(from: Variant, to: Variant) -> GdUnitVectorAssert

View File

@@ -0,0 +1 @@
uid://bcx6bgypklb3e

View File

@@ -0,0 +1,25 @@
# a value provider unsing a callback to get `next` value from a certain function
class_name CallBackValueProvider
extends ValueProvider
var _cb :Callable
var _args :Array
func _init(instance :Object, func_name :String, args :Array = Array(), force_error := true) -> void:
_cb = Callable(instance, func_name);
_args = args
if force_error and not _cb.is_valid():
push_error("Can't find function '%s' checked instance %s" % [func_name, instance])
func get_value() -> Variant:
if not _cb.is_valid():
return null
if _args.is_empty():
return await _cb.call()
return await _cb.callv(_args)
func dispose() -> void:
_cb = Callable()

View File

@@ -0,0 +1 @@
uid://r43u2usutiss

View File

@@ -0,0 +1,13 @@
# default value provider, simple returns the initial value
class_name DefaultValueProvider
extends ValueProvider
var _value: Variant
func _init(value: Variant) -> void:
_value = value
func get_value() -> Variant:
return _value

View File

@@ -0,0 +1 @@
uid://coauynw7rnsij

View File

@@ -0,0 +1,692 @@
class_name GdAssertMessages
extends Resource
const WARN_COLOR = "#EFF883"
const ERROR_COLOR = "#CD5C5C"
const VALUE_COLOR = "#1E90FF"
const SUB_COLOR := Color(1, 0, 0, .15)
const ADD_COLOR := Color(0, 1, 0, .15)
# Dictionary of control characters and their readable representations
const CONTROL_CHARS = {
"\n": "<LF>", # Line Feed
"\r": "<CR>", # Carriage Return
"\t": "<TAB>", # Tab
"\b": "<BS>", # Backspace
"\f": "<FF>", # Form Feed
"\v": "<VT>", # Vertical Tab
"\a": "<BEL>", # Bell
"": "<ESC>" # Escape
}
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):
as_rows[index] = " " + as_rows[index]
as_rows[-1] = " " + as_rows[-1]
return "\n".join(as_rows)
# improved version of InputEvent as text
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(), key_event.pressed, key_event.keycode, key_event.physical_keycode]
else:
text += event.as_text()
if event is InputEventMouse:
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" % [
mouse_event.shift_pressed,
mouse_event.alt_pressed,
mouse_event.ctrl_pressed,
mouse_event.meta_pressed,
mouse_event.command_or_control_autoremap]
return text
static func _colored_string_div(characters: String) -> String:
return colored_array_div(characters.to_utf32_buffer().to_int32_array())
static func colored_array_div(characters: PackedInt32Array) -> String:
if characters.is_empty():
return "<empty>"
var result := PackedInt32Array()
var index := 0
var missing_chars := PackedInt32Array()
var additional_chars := PackedInt32Array()
while index < characters.size():
var character := characters[index]
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():
result.append_array(format_chars(missing_chars, SUB_COLOR))
missing_chars = PackedInt32Array()
if not additional_chars.is_empty():
result.append_array(format_chars(additional_chars, ADD_COLOR))
additional_chars = PackedInt32Array()
@warning_ignore("return_value_discarded")
result.append(character)
index += 1
result.append_array(format_chars(missing_chars, SUB_COLOR))
result.append_array(format_chars(additional_chars, ADD_COLOR))
return result.to_byte_array().get_string_from_utf32()
static func _typed_value(value :Variant) -> String:
return GdDefaultValueDecoder.decode(value)
static func _warning(error :String) -> String:
return "[color=%s]%s[/color]" % [WARN_COLOR, error]
static func _error(error :String) -> String:
return "[color=%s]%s[/color]" % [ERROR_COLOR, error]
static func _nerror(number :Variant) -> String:
match typeof(number):
TYPE_INT:
return "[color=%s]%d[/color]" % [ERROR_COLOR, number]
TYPE_FLOAT:
return "[color=%s]%f[/color]" % [ERROR_COLOR, number]
_:
return "[color=%s]%s[/color]" % [ERROR_COLOR, str(number)]
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(str(value))]
TYPE_INT:
return "'[color=%s]%d[/color]'" % [VALUE_COLOR, value]
TYPE_FLOAT:
return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _typed_value(value)]
TYPE_COLOR:
return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _typed_value(value)]
TYPE_OBJECT:
if value == null:
return "'[color=%s]<null>[/color]'" % [VALUE_COLOR]
if value is InputEvent:
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, obj_value.get_class()]
TYPE_DICTIONARY:
return "'[color=%s]%s[/color]'" % [VALUE_COLOR, format_dict(value)]
_:
if GdArrayTools.is_array_type(value):
return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _typed_value(value)]
return "'[color=%s]%s[/color]'" % [VALUE_COLOR, value]
static func _index_report_as_table(index_reports :Array) -> String:
var table := "[table=3]$cells[/table]"
var header := "[cell][right][b]$text[/b][/right]\t[/cell]"
var cell := "[cell][right]$text[/right]\t[/cell]"
var cells := header.replace("$text", "Index") + header.replace("$text", "Current") + header.replace("$text", "Expected")
for report :Variant in index_reports:
var index :String = str(report["index"])
var current :String = str(report["current"])
var expected :String = str(report["expected"])
cells += cell.replace("$text", index) + cell.replace("$text", current) + cell.replace("$text", expected)
return table.replace("$cells", cells)
static func orphan_detected_on_suite_setup(count :int) -> String:
return "%s\n Detected <%d> orphan nodes during test suite setup stage! [b]Check before() and after()![/b]" % [
_warning("WARNING:"), count]
static func orphan_detected_on_test_setup(count :int) -> String:
return "%s\n Detected <%d> orphan nodes during test setup! [b]Check before_test() and after_test()![/b]" % [
_warning("WARNING:"), count]
static func orphan_detected_on_test(count :int) -> String:
return "%s\n Detected <%d> orphan nodes during test execution!" % [
_warning("WARNING:"), count]
static func fuzzer_interuped(iterations: int, error: String) -> String:
return "%s %s %s\n %s" % [
_error("Found an error after"),
_colored_value(iterations + 1),
_error("test iterations"),
error]
static func test_timeout(timeout :int) -> String:
return "%s\n %s" % [_error("Timeout !"), _colored_value("Test timed out after %s" % LocalTime.elapsed(timeout))]
# gdlint:disable = mixed-tabs-and-spaces
static func test_suite_skipped(hint :String, skip_count :int) -> String:
return """
%s
Skipped %s tests
Reason: %s
""".dedent().trim_prefix("\n")\
% [_error("The Entire test-suite is skipped!"), _colored_value(skip_count), _colored_value(hint)]
static func test_skipped(hint :String) -> String:
return """
%s
Reason: %s
""".dedent().trim_prefix("\n")\
% [_error("This test is skipped!"), _colored_value(hint)]
static func error_not_implemented() -> String:
return _error("Test not implemented!")
static func error_is_null(current :Variant) -> String:
return "%s %s but was %s" % [_error("Expecting:"), _colored_value(null), _colored_value(current)]
static func error_is_not_null() -> String:
return "%s %s" % [_error("Expecting: not to be"), _colored_value(null)]
static func error_equal(current :Variant, expected :Variant, index_reports :Array = []) -> String:
var report := """
%s
%s
but was
%s""".dedent().trim_prefix("\n") % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
if not index_reports.is_empty():
report += "\n\n%s\n%s" % [_error("Differences found:"), _index_report_as_table(index_reports)]
return report
static func error_not_equal(current :Variant, expected :Variant) -> String:
return "%s\n %s\n not equal to\n %s" % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
static func error_not_equal_case_insensetiv(current :Variant, expected :Variant) -> String:
return "%s\n %s\n not equal to (case insensitiv)\n %s" % [
_error("Expecting:"), _colored_value(expected), _colored_value(current)]
static func error_is_empty(current :Variant) -> String:
return "%s\n must be empty but was\n %s" % [_error("Expecting:"), _colored_value(current)]
static func error_is_not_empty() -> String:
return "%s\n must not be empty" % [_error("Expecting:")]
static func error_is_same(current :Variant, expected :Variant) -> String:
return "%s\n %s\n to refer to the same object\n %s" % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
@warning_ignore("unused_parameter")
static func error_not_same(_current :Variant, expected :Variant) -> String:
return "%s\n %s" % [_error("Expecting not same:"), _colored_value(expected)]
static func error_not_same_error(current :Variant, expected :Variant) -> String:
return "%s\n %s\n but was\n %s" % [_error("Expecting error message:"), _colored_value(expected), _colored_value(current)]
static func error_is_instanceof(current: GdUnitResult, expected :GdUnitResult) -> String:
return "%s\n %s\n But it was %s" % [_error("Expected instance of:"),\
_colored_value(expected.or_else(null)), _colored_value(current.or_else(null))]
# -- Boolean Assert specific messages -----------------------------------------------------
static func error_is_true(current :Variant) -> String:
return "%s %s but is %s" % [_error("Expecting:"), _colored_value(true), _colored_value(current)]
static func error_is_false(current :Variant) -> String:
return "%s %s but is %s" % [_error("Expecting:"), _colored_value(false), _colored_value(current)]
# - Integer/Float Assert specific messages -----------------------------------------------------
static func error_is_even(current :Variant) -> String:
return "%s\n %s must be even" % [_error("Expecting:"), _colored_value(current)]
static func error_is_odd(current :Variant) -> String:
return "%s\n %s must be odd" % [_error("Expecting:"), _colored_value(current)]
static func error_is_negative(current :Variant) -> String:
return "%s\n %s be negative" % [_error("Expecting:"), _colored_value(current)]
static func error_is_not_negative(current :Variant) -> String:
return "%s\n %s be not negative" % [_error("Expecting:"), _colored_value(current)]
static func error_is_zero(current :Variant) -> String:
return "%s\n equal to 0 but is %s" % [_error("Expecting:"), _colored_value(current)]
static func error_is_not_zero() -> String:
return "%s\n not equal to 0" % [_error("Expecting:")]
static func error_is_wrong_type(current_type :Variant.Type, expected_type :Variant.Type) -> String:
return "%s\n Expecting type %s but is %s" % [
_error("Unexpected type comparison:"),
_colored_value(GdObjects.type_as_string(current_type)),
_colored_value(GdObjects.type_as_string(expected_type))]
static func error_is_value(operation :int, current :Variant, expected :Variant, expected2 :Variant = null) -> String:
match operation:
Comparator.EQUAL:
return "%s\n %s but was '%s'" % [_error("Expecting:"), _colored_value(expected), _nerror(current)]
Comparator.LESS_THAN:
return "%s\n %s but was '%s'" % [_error("Expecting to be less than:"), _colored_value(expected), _nerror(current)]
Comparator.LESS_EQUAL:
return "%s\n %s but was '%s'" % [_error("Expecting to be less than or equal:"), _colored_value(expected), _nerror(current)]
Comparator.GREATER_THAN:
return "%s\n %s but was '%s'" % [_error("Expecting to be greater than:"), _colored_value(expected), _nerror(current)]
Comparator.GREATER_EQUAL:
return "%s\n %s but was '%s'" % [_error("Expecting to be greater than or equal:"), _colored_value(expected), _nerror(current)]
Comparator.BETWEEN_EQUAL:
return "%s\n %s\n in range between\n %s <> %s" % [
_error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)]
Comparator.NOT_BETWEEN_EQUAL:
return "%s\n %s\n not in range between\n %s <> %s" % [
_error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)]
return "TODO create expected message"
static func error_is_in(current :Variant, expected :Array) -> String:
return "%s\n %s\n is in\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(str(expected))]
static func error_is_not_in(current :Variant, expected :Array) -> String:
return "%s\n %s\n is not in\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(str(expected))]
# - StringAssert ---------------------------------------------------------------------------------
static func error_equal_ignoring_case(current :Variant, expected :Variant) -> String:
return "%s\n %s\n but was\n %s (ignoring case)" % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
static func error_contains(current :Variant, expected :Variant) -> String:
return "%s\n %s\n do contains\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
static func error_not_contains(current :Variant, expected :Variant) -> String:
return "%s\n %s\n not do contain\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
static func error_contains_ignoring_case(current :Variant, expected :Variant) -> String:
return "%s\n %s\n contains\n %s\n (ignoring case)" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
static func error_not_contains_ignoring_case(current :Variant, expected :Variant) -> String:
return "%s\n %s\n not do contains\n %s\n (ignoring case)" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
static func error_starts_with(current :Variant, expected :Variant) -> String:
return "%s\n %s\n to start with\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
static func error_ends_with(current :Variant, expected :Variant) -> String:
return "%s\n %s\n to end with\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
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:
return "%s\n %s but was '%s' in\n %s" % [
_error("Expecting size:"), _colored_value(expected), _nerror(current_length), _colored_value(current)]
Comparator.LESS_THAN:
return "%s\n %s but was '%s' in\n %s" % [
_error("Expecting size to be less than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)]
Comparator.LESS_EQUAL:
return "%s\n %s but was '%s' in\n %s" % [
_error("Expecting size to be less than or equal:"), _colored_value(expected),
_nerror(current_length), _colored_value(current)]
Comparator.GREATER_THAN:
return "%s\n %s but was '%s' in\n %s" % [
_error("Expecting size to be greater than:"), _colored_value(expected),
_nerror(current_length), _colored_value(current)]
Comparator.GREATER_EQUAL:
return "%s\n %s but was '%s' in\n %s" % [
_error("Expecting size to be greater than or equal:"), _colored_value(expected),
_nerror(current_length), _colored_value(current)]
return "TODO create expected message"
# - ArrayAssert specific messgaes ---------------------------------------------------
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 is_empty(not_expect):
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
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:
var failure_message := (
"Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
else "Expecting contains SAME exactly elements:"
)
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 is_empty(not_expect):
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
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: 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:"
)
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 is_empty(not_expect):
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
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: 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 is_empty(found):
error += "\n but found elements:\n %s" % _colored_value(found)
return error
# - DictionaryAssert specific messages ----------------------------------------------
static func error_contains_keys(current :Array, expected :Array, keys_not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String:
var failure := (
"Expecting contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
else "Expecting contains SAME keys:"
)
return "%s\n %s\n to contains:\n %s\n but can't find key's:\n %s" % [
_error(failure), _colored_value(current), _colored_value(expected), _colored_value(keys_not_found)]
static func error_not_contains_keys(current :Array, expected :Array, keys_not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String:
var failure := (
"Expecting NOT contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
else "Expecting NOT contains SAME keys"
)
return "%s\n %s\n do not contains:\n %s\n but contains key's:\n %s" % [
_error(failure), _colored_value(current), _colored_value(expected), _colored_value(keys_not_found)]
static func error_contains_key_value(key :Variant, value :Variant, current_value :Variant, compare_mode :GdObjects.COMPARE_MODE) -> String:
var failure := (
"Expecting contains key and value:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
else "Expecting contains SAME key and value:"
)
return "%s\n %s : %s\n but contains\n %s : %s" % [
_error(failure), _colored_value(key), _colored_value(value), _colored_value(key), _colored_value(current_value)]
# - ResultAssert specific errors ----------------------------------------------------
static func error_result_is_empty(current :GdUnitResult) -> String:
return _result_error_message(current, GdUnitResult.EMPTY)
static func error_result_is_success(current :GdUnitResult) -> String:
return _result_error_message(current, GdUnitResult.SUCCESS)
static func error_result_is_warning(current :GdUnitResult) -> String:
return _result_error_message(current, GdUnitResult.WARN)
static func error_result_is_error(current :GdUnitResult) -> String:
return _result_error_message(current, GdUnitResult.ERROR)
static func error_result_has_message(current :String, expected :String) -> String:
return "%s\n %s\n but was\n %s." % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
static func error_result_has_message_on_success(expected :String) -> String:
return "%s\n %s\n but the GdUnitResult is a success." % [_error("Expecting:"), _colored_value(expected)]
static func error_result_is_value(current :Variant, expected :Variant) -> String:
return "%s\n %s\n but was\n %s." % [_error("Expecting to contain same value:"), _colored_value(expected), _colored_value(current)]
static func _result_error_message(current :GdUnitResult, expected_type :int) -> String:
if current == null:
return _error("Expecting the result must be a %s but was <null>." % result_type(expected_type))
if current.is_success():
return _error("Expecting the result must be a %s but was SUCCESS." % result_type(expected_type))
var error := "Expecting the result must be a %s but was %s:" % [result_type(expected_type), result_type(current._state)]
return "%s\n %s" % [_error(error), _colored_value(result_message(current))]
static func error_interrupted(func_name :String, expected :Variant, elapsed :String) -> String:
func_name = humanized(func_name)
if expected == null:
return "%s %s but timed out after %s" % [_error("Expected:"), func_name, elapsed]
return "%s %s %s but timed out after %s" % [_error("Expected:"), func_name, _colored_value(expected), elapsed]
static func error_wait_signal(signal_name :String, args :Array, elapsed :String) -> String:
if args.is_empty():
return "%s %s but timed out after %s" % [
_error("Expecting emit signal:"), _colored_value(signal_name + "()"), elapsed]
return "%s %s but timed out after %s" % [
_error("Expecting emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed]
static func error_signal_emitted(signal_name :String, args :Array, elapsed :String) -> String:
if args.is_empty():
return "%s %s but is emitted after %s" % [
_error("Expecting do not emit signal:"), _colored_value(signal_name + "()"), elapsed]
return "%s %s but is emitted after %s" % [
_error("Expecting do not emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed]
static func error_await_signal_on_invalid_instance(source :Variant, signal_name :String, args :Array) -> String:
return "%s\n await_signal_on(%s, %s, %s)" % [
_error("Invalid source! Can't await on signal:"), _colored_value(source), signal_name, args]
static func result_type(type :int) -> String:
match type:
GdUnitResult.SUCCESS: return "SUCCESS"
GdUnitResult.WARN: return "WARNING"
GdUnitResult.ERROR: return "ERROR"
GdUnitResult.EMPTY: return "EMPTY"
return "UNKNOWN"
static func result_message(result :GdUnitResult) -> String:
match result._state:
GdUnitResult.SUCCESS: return ""
GdUnitResult.WARN: return result.warn_message()
GdUnitResult.ERROR: return result.error_message()
GdUnitResult.EMPTY: return ""
return "UNKNOWN"
# -----------------------------------------------------------------------------------
# - Spy|Mock specific errors ----------------------------------------------------
static func error_no_more_interactions(summary :Dictionary) -> String:
var interactions := PackedStringArray()
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 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(collected_interactions)]
static func _format_arguments(args :Array, times :int) -> String:
var fname :String = args[0]
var fargs := args.slice(1) as Array
var typed_args := _to_typed_args(fargs)
var fsignature := _colored_value("%s(%s)" % [fname, ", ".join(typed_args)])
return " %s %d time's" % [fsignature, times]
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:
var ie: InputEvent = arg
return input_event_as_text(ie)
return str(arg)
static func _find_first_diff(left :Array, right :Array) -> String:
for index in left.size():
var l :Variant = left[index]
var r :Variant = "<no entry>" if index >= right.size() else right[index]
if not GdObjects.equals(l, r):
return "at position %s\n '%s' vs '%s'" % [_colored_value(index), _typed_value(l), _typed_value(r)]
return ""
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)]
static func error_contains_exactly(current: Array, expected: Array) -> String:
return "%s\n %s\n but was\n %s" % [_error("Expecting exactly equal:"), _colored_value(expected), _colored_value(current)]
static func format_chars(characters: PackedInt32Array, type: Color) -> PackedInt32Array:
if characters.size() == 0:# or characters[0] == 10:
return characters
# Replace each control character with its readable form
var formatted_text := characters.to_byte_array().get_string_from_utf32()
for control_char: String in CONTROL_CHARS:
var replace_text: String = CONTROL_CHARS[control_char]
formatted_text = formatted_text.replace(control_char, replace_text)
# Handle special ASCII control characters (0x00-0x1F, 0x7F)
var ascii_text := ""
for i in formatted_text.length():
var character := formatted_text[i]
var code := character.unicode_at(0)
if code < 0x20 and not CONTROL_CHARS.has(character): # Control characters not handled above
ascii_text += "<0x%02X>" % code
elif code == 0x7F: # DEL character
ascii_text += "<DEL>"
else:
ascii_text += character
var message := "[bgcolor=#%s][color=white]%s[/color][/bgcolor]" % [
type.to_html(),
ascii_text
]
var result := PackedInt32Array()
result.append_array(message.to_utf32_buffer().to_int32_array())
return result
static func format_invalid(value :String) -> String:
return "[bgcolor=#%s][color=with]%s[/color][/bgcolor]" % [SUB_COLOR.to_html(), value]
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()

View File

@@ -0,0 +1 @@
uid://vl7cfc01g5wl

View File

@@ -0,0 +1,54 @@
class_name GdAssertReports
extends RefCounted
const LAST_ERROR = "last_assert_error_message"
const LAST_ERROR_LINE = "last_assert_error_line"
static func report_success() -> void:
GdUnitSignals.instance().gdunit_set_test_failed.emit(false)
GdAssertReports.set_last_error_line_number(-1)
Engine.remove_meta(LAST_ERROR)
static func report_warning(message :String, line_number :int) -> void:
GdUnitSignals.instance().gdunit_set_test_failed.emit(false)
send_report(GdUnitReport.new().create(GdUnitReport.WARN, line_number, message))
static func report_error(message:String, line_number :int) -> void:
GdUnitSignals.instance().gdunit_set_test_failed.emit(true)
GdAssertReports.set_last_error_line_number(line_number)
Engine.set_meta(LAST_ERROR, message)
# if we expect to fail we handle as success test
if _do_expect_assert_failing():
return
send_report(GdUnitReport.new().create(GdUnitReport.FAILURE, line_number, message))
static func reset_last_error_line_number() -> void:
Engine.remove_meta(LAST_ERROR_LINE)
static func set_last_error_line_number(line_number :int) -> void:
Engine.set_meta(LAST_ERROR_LINE, line_number)
static func get_last_error_line_number() -> int:
if Engine.has_meta(LAST_ERROR_LINE):
return Engine.get_meta(LAST_ERROR_LINE)
return -1
static func _do_expect_assert_failing() -> bool:
if Engine.has_meta(GdUnitConstants.EXPECT_ASSERT_REPORT_FAILURES):
return Engine.get_meta(GdUnitConstants.EXPECT_ASSERT_REPORT_FAILURES)
return false
static func current_failure() -> String:
return Engine.get_meta(LAST_ERROR)
static func send_report(report :GdUnitReport) -> void:
GdUnitThreadManager.get_current_context().get_execution_context().add_report(report)

View File

@@ -0,0 +1 @@
uid://brxvavm3ml0om

View File

@@ -0,0 +1 @@
uid://bx7cehfdh2x4w

View File

@@ -0,0 +1,80 @@
class_name GdUnitAssertImpl
extends GdUnitAssert
var _current :Variant
var _current_failure_message :String = ""
var _custom_failure_message :String = ""
var _additional_failure_message: String = ""
func _init(current :Variant) -> void:
_current = current
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
GdAssertReports.reset_last_error_line_number()
func failure_message() -> String:
return _current_failure_message
func current_value() -> Variant:
return _current
func report_success() -> GdUnitAssert:
GdAssertReports.report_success()
return self
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_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 do_fail() -> GdUnitAssert:
return report_error(GdAssertMessages.error_not_implemented())
func override_failure_message(message: String) -> GdUnitAssert:
_custom_failure_message = message
return self
func append_failure_message(message: String) -> GdUnitAssert:
_additional_failure_message = message
return self
func is_null() -> GdUnitAssert:
var current :Variant = current_value()
if current != null:
return report_error(GdAssertMessages.error_is_null(current))
return report_success()
func is_not_null() -> GdUnitAssert:
var current :Variant = current_value()
if current == null:
return report_error(GdAssertMessages.error_is_not_null())
return report_success()
func is_equal(expected: Variant) -> GdUnitAssert:
var current: Variant = current_value()
if not GdObjects.equals(current, expected):
return report_error(GdAssertMessages.error_equal(current, expected))
return report_success()
func is_not_equal(expected: Variant) -> GdUnitAssert:
var current: Variant = current_value()
if GdObjects.equals(current, expected):
return report_error(GdAssertMessages.error_not_equal(current, expected))
return report_success()

View File

@@ -0,0 +1 @@
uid://cq38mcld2thyl

View File

@@ -0,0 +1,68 @@
# Preloads all GdUnit assertions
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
@warning_ignore_start("return_value_discarded")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd")
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd")
@warning_ignore_restore("return_value_discarded")
### We now load all used asserts and tool scripts into the cache according to the principle of "lazy loading"
### in order to noticeably reduce the loading time of the test suite.
# We go this hard way to increase the loading performance to avoid reparsing all the used scripts
# for more detailed info -> https://github.com/godotengine/godot/issues/67400
# gdlint:disable=function-name
static func __lazy_load(script_path :String) -> GDScript:
return ResourceLoader.load(script_path, "GDScript", ResourceLoader.CACHE_MODE_REUSE)
static func validate_value_type(value :Variant, type :Variant.Type) -> bool:
return value == null or typeof(value) == type
# Scans the current stack trace for the root cause to extract the line number
static func get_line_number() -> int:
var stack_trace := get_stack()
if stack_trace == null or stack_trace.is_empty():
return -1
for index in stack_trace.size():
var stack_info :Dictionary = stack_trace[index]
var function :String = stack_info.get("function")
# we catch helper asserts to skip over to return the correct line number
if function.begins_with("assert_"):
continue
if function.begins_with("test_"):
return stack_info.get("line")
var source :String = stack_info.get("source")
if source.is_empty() \
or source.begins_with("user://") \
or source.ends_with("GdUnitAssert.gd") \
or source.ends_with("GdUnitAssertions.gd") \
or source.ends_with("AssertImpl.gd") \
or source.ends_with("GdUnitTestSuite.gd") \
or source.ends_with("GdUnitSceneRunnerImpl.gd") \
or source.ends_with("GdUnitObjectInteractions.gd") \
or source.ends_with("GdUnitObjectInteractionsVerifier.gd") \
or source.ends_with("GdUnitAwaiter.gd"):
continue
return stack_info.get("line")
return -1

View File

@@ -0,0 +1 @@
uid://61d7pdgldg0r

View File

@@ -0,0 +1,87 @@
extends GdUnitBoolAssert
var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
_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))
func _notification(event :int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
_base = null
func current_value() -> Variant:
return _base.current_value()
func report_success() -> GdUnitBoolAssert:
@warning_ignore("return_value_discarded")
_base.report_success()
return self
func report_error(error :String) -> GdUnitBoolAssert:
@warning_ignore("return_value_discarded")
_base.report_error(error)
return self
func failure_message() -> String:
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
func is_null() -> GdUnitBoolAssert:
@warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitBoolAssert:
@warning_ignore("return_value_discarded")
_base.is_not_null()
return self
func is_equal(expected: Variant) -> GdUnitBoolAssert:
@warning_ignore("return_value_discarded")
_base.is_equal(expected)
return self
func is_not_equal(expected: Variant) -> GdUnitBoolAssert:
@warning_ignore("return_value_discarded")
_base.is_not_equal(expected)
return self
func is_true() -> GdUnitBoolAssert:
if current_value() != true:
return report_error(GdAssertMessages.error_is_true(current_value()))
return report_success()
func is_false() -> GdUnitBoolAssert:
if current_value() == true || current_value() == null:
return report_error(GdAssertMessages.error_is_false(current_value()))
return report_success()

View File

@@ -0,0 +1 @@
uid://cxndss6mdq7de

View File

@@ -0,0 +1 @@
uid://dqrp7csbeyvon

View File

@@ -0,0 +1,136 @@
extends GdUnitFailureAssert
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
var _is_failed := false
var _failure_message: String
var _current_failure_message := ""
var _custom_failure_message := ""
var _additional_failure_message := ""
func _set_do_expect_fail(enabled :bool = true) -> void:
Engine.set_meta(GdUnitConstants.EXPECT_ASSERT_REPORT_FAILURES, enabled)
func execute_and_await(assertion :Callable, do_await := true) -> GdUnitFailureAssert:
# do not report any failure from the original assertion we want to test
_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:
await assertion.call()
else:
assertion.call()
_set_do_expect_fail(false)
# get the assert instance from current tread context
var current_assert := thread_context.get_assert()
if not is_instance_of(current_assert, GdUnitAssert):
_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
func _on_test_failed(value :bool) -> void:
_is_failed = value
func is_equal(_expected: Variant) -> GdUnitFailureAssert:
return _report_error("Not implemented")
func is_not_equal(_expected: Variant) -> GdUnitFailureAssert:
return _report_error("Not implemented")
func is_null() -> GdUnitFailureAssert:
return _report_error("Not implemented")
func is_not_null() -> GdUnitFailureAssert:
return _report_error("Not implemented")
func override_failure_message(message: String) -> GdUnitFailureAssert:
_custom_failure_message = message
return self
func append_failure_message(message: String) -> GdUnitFailureAssert:
_additional_failure_message = message
return self
func is_success() -> GdUnitFailureAssert:
if _is_failed:
return _report_error("Expect: assertion ends successfully.")
return self
func is_failed() -> GdUnitFailureAssert:
if not _is_failed:
return _report_error("Expect: assertion fails.")
return self
func has_line(expected :int) -> GdUnitFailureAssert:
var current := GdAssertReports.get_last_error_line_number()
if current != expected:
return _report_error("Expect: to failed on line '%d'\n but was '%d'." % [expected, current])
return self
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])
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
func starts_with_message(expected :String) -> GdUnitFailureAssert:
var expected_error := GdUnitTools.normalize_text(expected)
var current_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(_failure_message))
if current_error.find(expected_error) != 0:
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
func _report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert:
var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number()
_current_failure_message = GdAssertMessages.build_failure_message(error_message, _additional_failure_message, _custom_failure_message)
GdAssertReports.report_error(_current_failure_message, line_number)
return self
func _report_success() -> GdUnitFailureAssert:
GdAssertReports.report_success()
return self

View File

@@ -0,0 +1 @@
uid://cbrj7dsr235i0

View File

@@ -0,0 +1,116 @@
extends GdUnitFileAssert
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
_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))
func _notification(event :int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
_base = null
func current_value() -> String:
return _base.current_value()
func report_success() -> GdUnitFileAssert:
@warning_ignore("return_value_discarded")
_base.report_success()
return self
func report_error(error :String) -> GdUnitFileAssert:
@warning_ignore("return_value_discarded")
_base.report_error(error)
return self
func failure_message() -> String:
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_null() -> GdUnitFileAssert:
@warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitFileAssert:
@warning_ignore("return_value_discarded")
_base.is_not_null()
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
func is_file() -> GdUnitFileAssert:
var current := current_value()
if FileAccess.open(current, FileAccess.READ) == null:
return report_error("Is not a file '%s', error code %s" % [current, FileAccess.get_open_error()])
return report_success()
func exists() -> GdUnitFileAssert:
var current := current_value()
if not FileAccess.file_exists(current):
return report_error("The file '%s' not exists" %current)
return report_success()
func is_script() -> 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)
if not script is GDScript:
return report_error("The file '%s' is not a GdScript" % current)
return report_success()
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: GDScript = load(current)
if script is GDScript:
var source_code := GdScriptParser.to_unix_format(script.source_code)
var rows := Array(source_code.split("\n"))
@warning_ignore("return_value_discarded")
GdUnitArrayAssertImpl.new(rows).contains_exactly(expected_rows)
return self

View File

@@ -0,0 +1 @@
uid://2s6h0titid8y

View File

@@ -0,0 +1,159 @@
extends GdUnitFloatAssert
var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
_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))
func _notification(event :int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
_base = null
func current_value() -> Variant:
return _base.current_value()
func report_success() -> GdUnitFloatAssert:
@warning_ignore("return_value_discarded")
_base.report_success()
return self
func report_error(error :String) -> GdUnitFloatAssert:
@warning_ignore("return_value_discarded")
_base.report_error(error)
return self
func failure_message() -> String:
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: Variant) -> GdUnitFloatAssert:
@warning_ignore("return_value_discarded")
_base.is_equal(expected)
return self
func is_not_equal(expected: Variant) -> GdUnitFloatAssert:
@warning_ignore("return_value_discarded")
_base.is_not_equal(expected)
return self
@warning_ignore("shadowed_global_identifier")
func is_equal_approx(expected :float, approx :float) -> GdUnitFloatAssert:
return is_between(expected-approx, expected+approx)
func is_less(expected :float) -> GdUnitFloatAssert:
var current :Variant = current_value()
if current == null or current >= expected:
return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected))
return report_success()
func is_less_equal(expected :float) -> GdUnitFloatAssert:
var current :Variant = current_value()
if current == null or current > expected:
return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected))
return report_success()
func is_greater(expected :float) -> GdUnitFloatAssert:
var current :Variant = current_value()
if current == null or current <= expected:
return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected))
return report_success()
func is_greater_equal(expected :float) -> GdUnitFloatAssert:
var current :Variant = current_value()
if current == null or current < expected:
return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected))
return report_success()
func is_negative() -> GdUnitFloatAssert:
var current :Variant = current_value()
if current == null or current >= 0.0:
return report_error(GdAssertMessages.error_is_negative(current))
return report_success()
func is_not_negative() -> GdUnitFloatAssert:
var current :Variant = current_value()
if current == null or current < 0.0:
return report_error(GdAssertMessages.error_is_not_negative(current))
return report_success()
func is_zero() -> GdUnitFloatAssert:
var current :Variant = current_value()
@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()
@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()
func is_in(expected :Array) -> GdUnitFloatAssert:
var current :Variant = current_value()
if not expected.has(current):
return report_error(GdAssertMessages.error_is_in(current, expected))
return report_success()
func is_not_in(expected :Array) -> GdUnitFloatAssert:
var current :Variant = current_value()
if expected.has(current):
return report_error(GdAssertMessages.error_is_not_in(current, expected))
return report_success()
func is_between(from :float, to :float) -> GdUnitFloatAssert:
var current :Variant = current_value()
if current == null or current < from or current > to:
return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to))
return report_success()

View File

@@ -0,0 +1 @@
uid://dvce6xeybbh1i

View File

@@ -0,0 +1 @@
uid://c2jdw0vv5nldq

View File

@@ -0,0 +1,141 @@
extends GdUnitGodotErrorAssert
var _current_failure_message := ""
var _custom_failure_message := ""
var _additional_failure_message := ""
var _callable: Callable
func _init(callable: Callable) -> void:
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
GdAssertReports.reset_last_error_line_number()
_callable = callable
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()
return await _error_monitor().scan(true)
func _error_monitor() -> GodotGdErrorMonitor:
return GdUnitThreadManager.get_current_context().get_execution_context().error_monitor
func failure_message() -> String:
return _current_failure_message
func _report_success() -> GdUnitAssert:
GdAssertReports.report_success()
return self
func _report_error(error_message: String, failure_line_number: int = -1) -> GdUnitAssert:
var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number()
_current_failure_message = GdAssertMessages.build_failure_message(error_message, _additional_failure_message, _custom_failure_message)
GdAssertReports.report_error(_current_failure_message, line_number)
return self
func _has_log_entry(log_entries: Array[ErrorLogEntry], type: ErrorLogEntry.TYPE, error: Variant) -> bool:
for entry in log_entries:
if entry._type == type and GdObjects.equals(entry._message, error):
# Erase the log entry we already handled it by this assertion, otherwise it will report at twice
_error_monitor().erase_log_entry(entry)
return true
return false
func _to_list(log_entries: Array[ErrorLogEntry]) -> String:
if log_entries.is_empty():
return "no errors"
if log_entries.size() == 1:
return log_entries[0]._message
var value := ""
for entry in log_entries:
value += "'%s'\n" % entry._message
return value
func is_null() -> GdUnitGodotErrorAssert:
return _report_error("Not implemented")
func is_not_null() -> GdUnitGodotErrorAssert:
return _report_error("Not implemented")
func is_equal(_expected: Variant) -> GdUnitGodotErrorAssert:
return _report_error("Not implemented")
func is_not_equal(_expected: Variant) -> GdUnitGodotErrorAssert:
return _report_error("Not implemented")
func override_failure_message(message: String) -> GdUnitGodotErrorAssert:
_custom_failure_message = message
return self
func append_failure_message(message: String) -> GdUnitGodotErrorAssert:
_additional_failure_message = message
return self
func is_success() -> GdUnitGodotErrorAssert:
var log_entries := await _execute()
if log_entries.is_empty():
return _report_success()
return _report_error("""
Expecting: no error's are ocured.
but found: '%s'
""".dedent().trim_prefix("\n") % _to_list(log_entries))
func is_runtime_error(expected_error: Variant) -> GdUnitGodotErrorAssert:
var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_error)
if result.is_error():
return _report_error(result.error_message())
var log_entries := await _execute()
if _has_log_entry(log_entries, ErrorLogEntry.TYPE.SCRIPT_ERROR, expected_error):
return _report_success()
return _report_error("""
Expecting: a runtime error is triggered.
message: '%s'
found: %s
""".dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)])
func is_push_warning(expected_warning: Variant) -> GdUnitGodotErrorAssert:
var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_warning)
if result.is_error():
return _report_error(result.error_message())
var log_entries := await _execute()
if _has_log_entry(log_entries, ErrorLogEntry.TYPE.PUSH_WARNING, expected_warning):
return _report_success()
return _report_error("""
Expecting: push_warning() is called.
message: '%s'
found: %s
""".dedent().trim_prefix("\n") % [expected_warning, _to_list(log_entries)])
func is_push_error(expected_error: Variant) -> GdUnitGodotErrorAssert:
var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_error)
if result.is_error():
return _report_error(result.error_message())
var log_entries := await _execute()
if _has_log_entry(log_entries, ErrorLogEntry.TYPE.PUSH_ERROR, expected_error):
return _report_success()
return _report_error("""
Expecting: push_error() is called.
message: '%s'
found: %s
""".dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)])

View File

@@ -0,0 +1 @@
uid://cyi6ooahncq7q

View File

@@ -0,0 +1,166 @@
extends GdUnitIntAssert
var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
_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))
func _notification(event :int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
_base = null
func current_value() -> Variant:
return _base.current_value()
func report_success() -> GdUnitIntAssert:
@warning_ignore("return_value_discarded")
_base.report_success()
return self
func report_error(error :String) -> GdUnitIntAssert:
@warning_ignore("return_value_discarded")
_base.report_error(error)
return self
func failure_message() -> String:
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: Variant) -> GdUnitIntAssert:
@warning_ignore("return_value_discarded")
_base.is_equal(expected)
return self
func is_not_equal(expected: Variant) -> GdUnitIntAssert:
@warning_ignore("return_value_discarded")
_base.is_not_equal(expected)
return self
func is_less(expected :int) -> GdUnitIntAssert:
var current :Variant = current_value()
if current == null or current >= expected:
return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected))
return report_success()
func is_less_equal(expected :int) -> GdUnitIntAssert:
var current :Variant = current_value()
if current == null or current > expected:
return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected))
return report_success()
func is_greater(expected :int) -> GdUnitIntAssert:
var current :Variant = current_value()
if current == null or current <= expected:
return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected))
return report_success()
func is_greater_equal(expected :int) -> GdUnitIntAssert:
var current :Variant = current_value()
if current == null or current < expected:
return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected))
return report_success()
func is_even() -> GdUnitIntAssert:
var current :Variant = current_value()
if current == null or current % 2 != 0:
return report_error(GdAssertMessages.error_is_even(current))
return report_success()
func is_odd() -> GdUnitIntAssert:
var current :Variant = current_value()
if current == null or current % 2 == 0:
return report_error(GdAssertMessages.error_is_odd(current))
return report_success()
func is_negative() -> GdUnitIntAssert:
var current :Variant = current_value()
if current == null or current >= 0:
return report_error(GdAssertMessages.error_is_negative(current))
return report_success()
func is_not_negative() -> GdUnitIntAssert:
var current :Variant = current_value()
if current == null or current < 0:
return report_error(GdAssertMessages.error_is_not_negative(current))
return report_success()
func is_zero() -> GdUnitIntAssert:
var current :Variant = current_value()
if current != 0:
return report_error(GdAssertMessages.error_is_zero(current))
return report_success()
func is_not_zero() -> GdUnitIntAssert:
var current :Variant= current_value()
if current == 0:
return report_error(GdAssertMessages.error_is_not_zero())
return report_success()
func is_in(expected :Array) -> GdUnitIntAssert:
var current :Variant = current_value()
if not expected.has(current):
return report_error(GdAssertMessages.error_is_in(current, expected))
return report_success()
func is_not_in(expected :Array) -> GdUnitIntAssert:
var current :Variant = current_value()
if expected.has(current):
return report_error(GdAssertMessages.error_is_not_in(current, expected))
return report_success()
func is_between(from :int, to :int) -> GdUnitIntAssert:
var current :Variant = current_value()
if current == null or current < from or current > to:
return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to))
return report_success()

View File

@@ -0,0 +1 @@
uid://j4mpmwm2hw61

View File

@@ -0,0 +1,166 @@
extends GdUnitObjectAssert
var _base: GdUnitAssertImpl
func _init(current: Variant) -> void:
_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 (GdUnitAssertions.validate_value_type(current, TYPE_BOOL)
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))
func _notification(event: int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
_base = null
func current_value() -> Variant:
return _base.current_value()
func report_success() -> GdUnitObjectAssert:
@warning_ignore("return_value_discarded")
_base.report_success()
return self
func report_error(error: String) -> GdUnitObjectAssert:
@warning_ignore("return_value_discarded")
_base.report_error(error)
return self
func failure_message() -> String:
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
@warning_ignore("shadowed_global_identifier")
func is_same(expected: Variant) -> GdUnitObjectAssert:
var current: Variant = current_value()
if not is_same(current, expected):
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):
return report_error(GdAssertMessages.error_not_same(current, expected))
return report_success()
func is_instanceof(type: Variant) -> GdUnitObjectAssert:
var current: Variant = current_value()
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)
return report_error(GdAssertMessages.error_is_instanceof(result_current, result_expected))
return report_success()
func is_not_instanceof(type: Variant) -> GdUnitObjectAssert:
var current: Variant = current_value()
if is_instance_of(current, type):
var result := GdObjects.extract_class_name(type)
if result.is_success():
return report_error("Expected not be a instance of <%s>" % str(result.value()))
push_error("Internal ERROR: %s" % result.error_message())
return self
return report_success()
## Checks whether the current object inherits from the specified type.
func is_inheriting(type: Variant) -> GdUnitObjectAssert:
var current: Variant = current_value()
if not is_instance_of(current, TYPE_OBJECT):
return report_error("Expected '%s' to inherit from at least Object." % str(current))
var result := _inherits(current, type)
if result.is_success():
return report_success()
return report_error(result.error_message())
## Checks whether the current object does NOT inherit from the specified type.
func is_not_inheriting(type: Variant) -> GdUnitObjectAssert:
var current: Variant = current_value()
if not is_instance_of(current, TYPE_OBJECT):
return report_error("Expected '%s' to inherit from at least Object." % str(current))
var result := _inherits(current, type)
if result.is_success():
return report_error("Expected type to not inherit from <%s>" % _extract_class_type(type))
return report_success()
func _inherits(current: Variant, type: Variant) -> GdUnitResult:
var type_as_string := _extract_class_type(type)
if type_as_string == "Object":
return GdUnitResult.success("")
var obj: Object = current
for p in obj.get_property_list():
var clazz_name :String = p["name"]
if p["usage"] == PROPERTY_USAGE_CATEGORY and clazz_name == p["hint_string"] and clazz_name == type_as_string:
return GdUnitResult.success("")
var script: Script = obj.get_script()
if script != null:
while script != null:
var result := GdObjects.extract_class_name(script)
if result.is_success() and result.value() == type_as_string:
return GdUnitResult.success("")
script = script.get_base_script()
return GdUnitResult.error("Expected type to inherit from <%s>" % type_as_string)
func _extract_class_type(type: Variant) -> String:
if type is String:
return type
var result := GdObjects.extract_class_name(type)
if result.is_error():
return ""
return result.value()

View File

@@ -0,0 +1 @@
uid://bm6qm58a0dacq

View File

@@ -0,0 +1,128 @@
extends GdUnitResultAssert
var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
_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))
func _notification(event :int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
_base = null
func validate_value_type(value :Variant) -> bool:
return value == null or value is GdUnitResult
func current_value() -> GdUnitResult:
return _base.current_value()
func report_success() -> GdUnitResultAssert:
@warning_ignore("return_value_discarded")
_base.report_success()
return self
func report_error(error :String) -> GdUnitResultAssert:
@warning_ignore("return_value_discarded")
_base.report_error(error)
return self
func failure_message() -> String:
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
func is_equal(expected: Variant) -> GdUnitResultAssert:
return is_value(expected)
func is_not_equal(expected: Variant) -> GdUnitResultAssert:
var result := current_value()
var value :Variant = null if result == null else result.value()
if GdObjects.equals(value, expected):
return report_error(GdAssertMessages.error_not_equal(value, expected))
return report_success()
func is_empty() -> GdUnitResultAssert:
var result := current_value()
if result == null or not result.is_empty():
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():
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():
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():
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:
return report_error(GdAssertMessages.error_result_has_message("<null>", expected))
if result.is_success():
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):
return report_error(GdAssertMessages.error_result_is_value(value, expected))
return report_success()

View File

@@ -0,0 +1 @@
uid://b0dlq6jyjcvps

View File

@@ -0,0 +1,143 @@
extends GdUnitSignalAssert
const DEFAULT_TIMEOUT := 2000
var _signal_collector :GdUnitSignalCollector
var _emitter :Object
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
func _init(emitter :Object) -> void:
# save the actual assert instance on the current thread context
var context := GdUnitThreadManager.get_current_context()
context.set_assert(self)
_signal_collector = context.get_signal_collector()
_line_number = GdUnitAssertions.get_line_number()
_emitter = emitter
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
func report_warning(message :String) -> GdUnitAssert:
GdAssertReports.report_warning(message, GdUnitAssertions.get_line_number())
return self
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_failure_message
func override_failure_message(message: String) -> GdUnitSignalAssert:
_custom_failure_message = message
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:
_timeout = timeout
return self
func is_null() -> GdUnitSignalAssert:
if _emitter != null:
return report_error(GdAssertMessages.error_is_null(_emitter))
return report_success()
func is_not_null() -> GdUnitSignalAssert:
if _emitter == null:
return report_error(GdAssertMessages.error_is_not_null())
return report_success()
func is_equal(_expected: Variant) -> GdUnitSignalAssert:
return report_error("Not implemented")
func is_not_equal(_expected: Variant) -> GdUnitSignalAssert:
return report_error("Not implemented")
# 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
# Verifies that given signal is emitted until waiting time
func is_emitted(name :String, args := []) -> GdUnitSignalAssert:
_line_number = GdUnitAssertions.get_line_number()
return await _wail_until_signal(name, args, false)
# Verifies that given signal is NOT emitted until waiting time
func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert:
_line_number = GdUnitAssertions.get_line_number()
return await _wail_until_signal(name, args, true)
func _wail_until_signal(signal_name :String, expected_args :Array, expect_not_emitted: bool) -> GdUnitSignalAssert:
if _emitter == null:
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):
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() 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() 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):
_signal_collector.reset_received_signals(_emitter, signal_name, expected_args)
return self

View File

@@ -0,0 +1 @@
uid://dlh37yc086vr5

View File

@@ -0,0 +1,208 @@
extends GdUnitStringAssert
var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
_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))
func _notification(event :int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
_base = null
func failure_message() -> String:
return _base.failure_message()
func current_value() -> Variant:
return _base.current_value()
func report_success() -> GdUnitStringAssert:
@warning_ignore("return_value_discarded")
_base.report_success()
return self
func report_error(error :String) -> GdUnitStringAssert:
@warning_ignore("return_value_discarded")
_base.report_error(error)
return self
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
func is_equal(expected: Variant) -> GdUnitStringAssert:
return _is_equal(expected, false, GdAssertMessages.error_equal)
func is_equal_ignoring_case(expected: Variant) -> GdUnitStringAssert:
return _is_equal(expected, true, GdAssertMessages.error_equal_ignoring_case)
@warning_ignore_start("unsafe_call_argument")
func _is_equal(expected: Variant, ignore_case: bool, message_cb: Callable) -> GdUnitStringAssert:
var current: Variant = current_value()
if current == null:
return report_error(message_cb.call(current, expected))
var cur_value := str(current)
if not GdObjects.equals(cur_value, expected, ignore_case):
var exp_value := str(expected)
if contains_bbcode(cur_value):
# mask user bbcode
# https://docs.godotengine.org/en/4.5/tutorials/ui/bbcode_in_richtextlabel.html#handling-user-input-safely
return report_error(message_cb.call(cur_value.replace("[", "[lb]"), exp_value.replace("[", "[lb]")))
var diffs := GdDiffTool.string_diff(cur_value, exp_value)
var formatted_current := GdAssertMessages.colored_array_div(diffs[1])
return report_error(message_cb.call(formatted_current, exp_value))
return report_success()
@warning_ignore_restore("unsafe_call_argument")
func is_not_equal(expected: Variant) -> GdUnitStringAssert:
var current: Variant = current_value()
if GdObjects.equals(current, expected):
return report_error(GdAssertMessages.error_not_equal(current, expected))
return report_success()
func is_not_equal_ignoring_case(expected :Variant) -> GdUnitStringAssert:
var current :Variant = current_value()
if GdObjects.equals(current, expected, true):
return report_error(GdAssertMessages.error_not_equal(current, expected))
return report_success()
func is_empty() -> GdUnitStringAssert:
var current :Variant = current_value()
@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()
@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()
@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()
@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()
@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()
@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()
@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()
func ends_with(expected :String) -> GdUnitStringAssert:
var current :Variant = current_value()
if current == null:
return report_error(GdAssertMessages.error_ends_with(current, expected))
@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()
# gdlint:disable=max-returns
func has_length(expected :int, comparator := Comparator.EQUAL) -> GdUnitStringAssert:
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 str_current.length() != expected:
return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
Comparator.LESS_THAN:
if str_current.length() >= expected:
return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
Comparator.LESS_EQUAL:
if str_current.length() > expected:
return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
Comparator.GREATER_THAN:
if str_current.length() <= expected:
return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
Comparator.GREATER_EQUAL:
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()
func contains_bbcode(value: String) -> bool:
var rtl := RichTextLabel.new()
rtl.bbcode_enabled = true
rtl.parse_bbcode(value)
var has_bbcode := rtl.get_parsed_text() != value
rtl.free()
return has_bbcode

View File

@@ -0,0 +1 @@
uid://dxqvilchqqeta

View File

@@ -0,0 +1,187 @@
extends GdUnitVectorAssert
var _base: GdUnitAssertImpl
var _current_type: int
var _type_check: bool
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)
func _notification(event :int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
_base = null
func _validate_value_type(value :Variant) -> bool:
return (
value == null
or typeof(value) in [
TYPE_VECTOR2,
TYPE_VECTOR2I,
TYPE_VECTOR3,
TYPE_VECTOR3I,
TYPE_VECTOR4,
TYPE_VECTOR4I
]
)
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
func current_value() -> Variant:
return _base.current_value()
func report_success() -> GdUnitVectorAssert:
@warning_ignore("return_value_discarded")
_base.report_success()
return self
func report_error(error :String) -> GdUnitVectorAssert:
@warning_ignore("return_value_discarded")
_base.report_error(error)
return self
func failure_message() -> String:
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 _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 _type_check and not _validate_is_vector_type(expected):
return self
@warning_ignore("return_value_discarded")
_base.is_not_equal(expected)
return self
@warning_ignore("shadowed_global_identifier")
func is_equal_approx(expected :Variant, approx :Variant) -> GdUnitVectorAssert:
if not _validate_is_vector_type(expected) or not _validate_is_vector_type(approx):
return self
var current :Variant = current_value()
var from :Variant = expected - approx
var to :Variant = expected + approx
if current == null or (not _is_equal_approx(current, from, to)):
return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to))
return report_success()
func _is_equal_approx(current :Variant, from :Variant, to :Variant) -> bool:
match typeof(current):
TYPE_VECTOR2, TYPE_VECTOR2I:
return ((current.x >= from.x and current.y >= from.y)
and (current.x <= to.x and current.y <= to.y))
TYPE_VECTOR3, TYPE_VECTOR3I:
return ((current.x >= from.x and current.y >= from.y and current.z >= from.z)
and (current.x <= to.x and current.y <= to.y and current.z <= to.z))
TYPE_VECTOR4, TYPE_VECTOR4I:
return ((current.x >= from.x and current.y >= from.y and current.z >= from.z and current.w >= from.w)
and (current.x <= to.x and current.y <= to.y and current.z <= to.z and current.w <= to.w))
_:
push_error("Missing implementation '_is_equal_approx' for vector type %s" % typeof(current))
return false
func is_less(expected :Variant) -> GdUnitVectorAssert:
if not _validate_is_vector_type(expected):
return self
var current :Variant = current_value()
if current == null or current >= expected:
return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected))
return report_success()
func is_less_equal(expected :Variant) -> GdUnitVectorAssert:
if not _validate_is_vector_type(expected):
return self
var current :Variant = current_value()
if current == null or current > expected:
return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected))
return report_success()
func is_greater(expected :Variant) -> GdUnitVectorAssert:
if not _validate_is_vector_type(expected):
return self
var current :Variant = current_value()
if current == null or current <= expected:
return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected))
return report_success()
func is_greater_equal(expected :Variant) -> GdUnitVectorAssert:
if not _validate_is_vector_type(expected):
return self
var current :Variant = current_value()
if current == null or current < expected:
return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected))
return report_success()
func is_between(from :Variant, to :Variant) -> GdUnitVectorAssert:
if not _validate_is_vector_type(from) or not _validate_is_vector_type(to):
return self
var current :Variant = current_value()
if current == null or not (current >= from and current <= to):
return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to))
return report_success()
func is_not_between(from :Variant, to :Variant) -> GdUnitVectorAssert:
if not _validate_is_vector_type(from) or not _validate_is_vector_type(to):
return self
var current :Variant = current_value()
if (current != null and current >= from and current <= to):
return report_error(GdAssertMessages.error_is_value(Comparator.NOT_BETWEEN_EQUAL, current, from, to))
return report_success()

View File

@@ -0,0 +1 @@
uid://r4avfcakvscw

View File

@@ -0,0 +1,10 @@
# base interface for assert value provider
class_name ValueProvider
extends RefCounted
func get_value() -> Variant:
return null
func dispose() -> void:
pass

View File

@@ -0,0 +1 @@
uid://8y15b6ts3kss

View File

@@ -0,0 +1,62 @@
class_name CmdArgumentParser
extends RefCounted
var _options :CmdOptions
var _tool_name :String
var _parsed_commands :Dictionary = Dictionary()
func _init(p_options :CmdOptions, p_tool_name :String) -> void:
_options = p_options
_tool_name = p_tool_name
func parse(args :Array, ignore_unknown_cmd := false) -> GdUnitResult:
_parsed_commands.clear()
# parse until first program argument
while not args.is_empty():
var arg :String = args.pop_front()
if arg.find(_tool_name) != -1:
break
if args.is_empty():
return GdUnitResult.empty()
# now parse all arguments
while not args.is_empty():
var cmd :String = args.pop_front()
var option := _options.get_option(cmd)
if option:
if _parse_cmd_arguments(option, args) == -1:
return GdUnitResult.error("The '%s' command requires an argument!" % option.short_command())
elif not ignore_unknown_cmd:
return GdUnitResult.error("Unknown '%s' command!" % cmd)
return GdUnitResult.success(_parsed_commands.values())
func options() -> CmdOptions:
return _options
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))
if option.has_argument():
if not option.is_argument_optional() and args.is_empty():
return -1
if _is_next_value_argument(args):
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: PackedStringArray) -> bool:
if args.is_empty():
return false
return _options.get_option(args[0]) == null

View File

@@ -0,0 +1 @@
uid://d4hd3vc50jltg

View File

@@ -0,0 +1,27 @@
class_name CmdCommand
extends RefCounted
var _name: String
var _arguments: PackedStringArray
func _init(p_name :String, p_arguments := []) -> void:
_name = p_name
_arguments = PackedStringArray(p_arguments)
func name() -> String:
return _name
func arguments() -> PackedStringArray:
return _arguments
func add_argument(arg :String) -> void:
@warning_ignore("return_value_discarded")
_arguments.append(arg)
func _to_string() -> String:
return "%s:%s" % [_name, ", ".join(_arguments)]

View File

@@ -0,0 +1 @@
uid://w4mr1j0k0l

View File

@@ -0,0 +1,136 @@
class_name CmdCommandHandler
extends RefCounted
const CB_SINGLE_ARG = 0
const CB_MULTI_ARGS = 1
const NO_CB := Callable()
var _cmd_options :CmdOptions
# holds the command callbacks by key:<cmd_name>:String and value: [<cb single arg>, <cb multible args>]:Array
# Dictionary[String, Array[Callback]
var _command_cbs :Dictionary
func _init(cmd_options: CmdOptions) -> void:
_cmd_options = cmd_options
# register a callback function for given command
# cmd_name short name of the command
# fr_arg a funcref to a function with a single argument
func register_cb(cmd_name: String, cb: Callable) -> CmdCommandHandler:
var registered_cb: Array = _command_cbs.get(cmd_name, [NO_CB, NO_CB])
if registered_cb[CB_SINGLE_ARG]:
push_error("A function for command '%s' is already registered!" % cmd_name)
return self
if not _validate_cb_signature(cb, TYPE_STRING):
push_error(
("The callback '%s:%s' for command '%s' has invalid function signature. "
+"The callback signature must be 'func name(value: PackedStringArray)'")
% [cb.get_object().get_class(), cb.get_method(), cmd_name])
return null
registered_cb[CB_SINGLE_ARG] = cb
_command_cbs[cmd_name] = registered_cb
return self
# register a callback function for given command
# cb a funcref to a function with a variable number of arguments but expects all parameters to be passed via a single Array.
func register_cbv(cmd_name: String, cb: Callable) -> CmdCommandHandler:
var registered_cb: Array = _command_cbs.get(cmd_name, [NO_CB, NO_CB])
if registered_cb[CB_MULTI_ARGS]:
push_error("A function for command '%s' is already registered!" % cmd_name)
return self
if not _validate_cb_signature(cb, TYPE_PACKED_STRING_ARRAY):
push_error(
("The callback '%s:%s' for command '%s' has invalid function signature. "
+"The callback signature must be 'func name(value: PackedStringArray)'")
% [cb.get_object().get_class(), cb.get_method(), cmd_name])
return null
registered_cb[CB_MULTI_ARGS] = cb
_command_cbs[cmd_name] = registered_cb
return self
func _validate() -> GdUnitResult:
var errors := PackedStringArray()
# Dictionary[StringName, String]
var registered_cbs := Dictionary()
for cmd_name in _command_cbs.keys() as Array[String]:
var cb: Callable = (_command_cbs[cmd_name][CB_SINGLE_ARG]
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 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
if errors.is_empty():
return GdUnitResult.success(true)
return GdUnitResult.error("\n".join(errors))
func execute(commands: Array[CmdCommand]) -> GdUnitResult:
var result := _validate()
if result.is_error():
return result
for cmd in commands:
var cmd_name := cmd.name()
if _command_cbs.has(cmd_name):
var cb_s: Callable = _command_cbs.get(cmd_name)[CB_SINGLE_ARG]
var arguments := cmd.arguments()
var cmd_option := _cmd_options.get_option(cmd_name)
if arguments.is_empty():
cb_s.call()
elif arguments.size() > 1:
var cb_m: Callable = _command_cbs.get(cmd_name)[CB_MULTI_ARGS]
cb_m.call(arguments)
else:
if cmd_option.type() == TYPE_BOOL:
cb_s.call(true if arguments[0] == "true" else false)
else:
cb_s.call(arguments[0])
return GdUnitResult.success(true)
func _validate_cb_signature(cb: Callable, arg_type: int) -> bool:
for m in cb.get_object().get_method_list():
if m["name"] == cb.get_method():
@warning_ignore("unsafe_cast")
return _validate_func_arguments(m["args"] as Array, arg_type)
return true
func _validate_func_arguments(arguments: Array, arg_type: int) -> bool:
# validate we have a single argument
if arguments.size() > 1:
return false
# a cb with no arguments is also valid
if arguments.size() == 0:
return true
# validate argument type
var arg: Dictionary = arguments[0]
@warning_ignore("unsafe_cast")
if arg["usage"] as int == PROPERTY_USAGE_NIL_IS_VARIANT:
return true
if arg["type"] != arg_type:
return false
return true

View File

@@ -0,0 +1 @@
uid://ccm3ivfiaf3i7

Some files were not shown because too many files have changed in this diff Show More