diff options
Diffstat (limited to 'addons/gdUnit4/src/asserts')
42 files changed, 3507 insertions, 0 deletions
diff --git a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd new file mode 100644 index 0000000..6be4b3e --- /dev/null +++ b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd @@ -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() diff --git a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd.uid b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd.uid new file mode 100644 index 0000000..20374e1 --- /dev/null +++ b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd.uid @@ -0,0 +1 @@ +uid://da0u7lr425ojb diff --git a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd new file mode 100644 index 0000000..2f828fa --- /dev/null +++ b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd @@ -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 diff --git a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd.uid b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd.uid new file mode 100644 index 0000000..0759472 --- /dev/null +++ b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd.uid @@ -0,0 +1 @@ +uid://d0oppd5oc256x diff --git a/addons/gdUnit4/src/asserts/GdAssertMessages.gd b/addons/gdUnit4/src/asserts/GdAssertMessages.gd new file mode 100644 index 0000000..5a2fa1c --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdAssertMessages.gd @@ -0,0 +1,752 @@ +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 : keycode=%s (%s) pressed: %s + physical_keycode: %s + location: %s + echo: %s""" % [ + key_event.keycode, + key_event.as_text_keycode(), + key_event.pressed, + key_event.physical_keycode, + key_event.location, + key_event.echo] + 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 += """ + -------- + mods: %s + shift: %s + alt: %s + control: %s + meta: %s + command: %s""" % [ + mouse_event.get_modifiers_mask(), + mouse_event.shift_pressed, + mouse_event.alt_pressed, + mouse_event.ctrl_pressed, + mouse_event.meta_pressed, + mouse_event.command_or_control_autoremap] + return text.dedent() + + +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: Variant, color: Color) -> String: + return "[color=%s]%s[/color]" % [color.to_html(), value] + + +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_warning(orphans_count: int) -> String: + return """ + %s: Found %s possible orphan nodes. + Add %s to the end of the test to collect details.""".dedent().trim_prefix("\n") % [ + _warning("WARNING:"), + _nerror(orphans_count), + _colored_value("collect_orphan_node_details()") + ] + +static func orphan_detected_on_suite_setup(orphans: Array[GdUnitOrphanNodeInfo]) -> String: + return """ + %s Detected %s orphan nodes! + [b]Verify your test suite setup.[/b] + %s""".dedent().trim_prefix("\n") % [ + _warning("WARNING:"), + _nerror(orphans.size()), + _build_orphan_node_stacktrace(orphans)] + + +static func orphan_detected_on_test_setup(orphans: Array[GdUnitOrphanNodeInfo]) -> String: + return """ + %s Detected %s orphan nodes on test setup! + [b]Check before_test() and after_test()![/b] + %s""".dedent().trim_prefix("\n") % [ + _warning("WARNING:"), + _nerror(orphans.size()), + _build_orphan_node_stacktrace(orphans) + ] + + +static func orphan_detected_on_test(orphans: Array[GdUnitOrphanNodeInfo]) -> String: + return """ + %s Detected %s orphan nodes! + %s""".dedent().trim_prefix("\n") % [ + _warning("WARNING:"), + _nerror(orphans.size()), + _build_orphan_node_stacktrace(orphans) + ] + + +static func _build_orphan_node_stacktrace(orphans: Array[GdUnitOrphanNodeInfo]) -> String: + var stack_trace := "\n" + for orphan in orphans: + stack_trace += orphan.as_trace(orphan, true) + "\n" + return stack_trace.indent(" ") + + + +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))] + + +static func test_session_terminated() -> String: + return "%s" % _error("Test Session Terminated") + + +# 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() diff --git a/addons/gdUnit4/src/asserts/GdAssertMessages.gd.uid b/addons/gdUnit4/src/asserts/GdAssertMessages.gd.uid new file mode 100644 index 0000000..f1336de --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdAssertMessages.gd.uid @@ -0,0 +1 @@ +uid://cf4cfpmy5gb5s diff --git a/addons/gdUnit4/src/asserts/GdAssertReports.gd b/addons/gdUnit4/src/asserts/GdAssertReports.gd new file mode 100644 index 0000000..06b72ed --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdAssertReports.gd @@ -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) diff --git a/addons/gdUnit4/src/asserts/GdAssertReports.gd.uid b/addons/gdUnit4/src/asserts/GdAssertReports.gd.uid new file mode 100644 index 0000000..10cae25 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdAssertReports.gd.uid @@ -0,0 +1 @@ +uid://cnmp765i0d7kk diff --git a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd new file mode 100644 index 0000000..63b45a0 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd @@ -0,0 +1,424 @@ +class_name GdUnitArrayAssertImpl +extends GdUnitArrayAssert + + +var _base: GdUnitAssertImpl +var _current_value_provider: ValueProvider +var _type_check: bool + + +func _init(current: Variant, type_check := true) -> void: + _type_check = type_check + _current_value_provider = DefaultValueProvider.new(current) + _base = GdUnitAssertImpl.new(current) + # save the actual assert instance on the current thread context + GdUnitThreadManager.get_current_context().set_assert(self) + if not _validate_value_type(current): + @warning_ignore("return_value_discarded") + report_error("GdUnitArrayAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) + + +func _notification(event: int) -> void: + if event == NOTIFICATION_PREDELETE: + if _base != null: + _base.notification(event) + _base = null + + +func report_success() -> GdUnitArrayAssert: + @warning_ignore("return_value_discarded") + _base.report_success() + return self + + +func report_error(error: String) -> GdUnitArrayAssert: + @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) -> GdUnitArrayAssert: + @warning_ignore("return_value_discarded") + _base.override_failure_message(message) + return self + + +func append_failure_message(message: String) -> GdUnitArrayAssert: + @warning_ignore("return_value_discarded") + _base.append_failure_message(message) + return self + + +func _validate_value_type(value: Variant) -> bool: + return value == null or GdArrayTools.is_array_type(value) + + +func get_current_value() -> Variant: + return _current_value_provider.get_value() + + +func max_length(left: Variant, right: Variant) -> int: + var ls := str(left).length() + var rs := str(right).length() + return rs if ls < rs else ls + + +# gdlint: disable=function-name +func _toPackedStringArray(value: Variant) -> PackedStringArray: + if GdArrayTools.is_array_type(value): + @warning_ignore("unsafe_cast") + return PackedStringArray(value as Array) + return PackedStringArray([str(value)]) + + +func _array_equals_div(current: Variant, expected: Variant, case_sensitive: bool = false) -> Array: + var current_value := _toPackedStringArray(current) + var expected_value := _toPackedStringArray(expected) + var index_report := Array() + for index in current_value.size(): + var c := current_value[index] + if index < expected_value.size(): + var e := expected_value[index] + if not GdObjects.equals(c, e, case_sensitive): + var length := max_length(c, e) + current_value[index] = GdAssertMessages.format_invalid(c.lpad(length)) + expected_value[index] = e.lpad(length) + index_report.push_back({"index": index, "current": c, "expected": e}) + else: + current_value[index] = GdAssertMessages.format_invalid(c) + index_report.push_back({"index": index, "current": c, "expected": "<N/A>"}) + + for index in range(current_value.size(), expected_value.size()): + var value := expected_value[index] + expected_value[index] = GdAssertMessages.format_invalid(value) + index_report.push_back({"index": index, "current": "<N/A>", "expected": value}) + return [current_value, expected_value, index_report] + + +func _array_div(compare_mode: GdObjects.COMPARE_MODE, left: Array[Variant], right: Array[Variant], _same_order := false) -> Array[Variant]: + var not_expect := left.duplicate(true) + var not_found := right.duplicate(true) + for index_c in left.size(): + var c: Variant = left[index_c] + for index_e in right.size(): + var e: Variant = right[index_e] + if GdObjects.equals(c, e, false, compare_mode): + GdArrayTools.erase_value(not_expect, e) + GdArrayTools.erase_value(not_found, c) + break + return [not_expect, not_found] + + +func _contains(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: + var by_reference := compare_mode == GdObjects.COMPARE_MODE.OBJECT_REFERENCE + var current_value: Variant = get_current_value() + var expected_value: Variant = _extract_variadic_value(expected) + if not _validate_value_type(expected_value): + return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value)) + + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains(current_value, expected_value, [], expected_value, by_reference)) + @warning_ignore("unsafe_cast") + var diffs := _array_div(compare_mode, current_value as Array[Variant], expected_value as Array[Variant]) + #var not_expect := diffs[0] as Array + var not_found: Array = diffs[1] + if not not_found.is_empty(): + return report_error(GdAssertMessages.error_arr_contains(current_value, expected_value, [], not_found, by_reference)) + return report_success() + + +func _contains_exactly(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: + var current_value: Variant = get_current_value() + var expected_value: Variant = _extract_variadic_value(expected) + if not _validate_value_type(expected_value): + return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value)) + + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly(null, expected_value, [], expected_value, compare_mode)) + # has same content in same order + if _is_equal(current_value, expected_value, false, compare_mode): + return report_success() + # check has same elements but in different order + if _is_equals_sorted(current_value, expected_value, false, compare_mode): + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected_value, [], [], compare_mode)) + # find the difference + @warning_ignore("unsafe_cast") + var diffs := _array_div(compare_mode, + current_value as Array[Variant], + expected_value as Array[Variant], + GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + var not_expect: Array[Variant] = diffs[0] + var not_found: Array[Variant] = diffs[1] + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected_value, not_expect, not_found, compare_mode)) + + +func _contains_exactly_in_any_order(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: + var current_value: Variant = get_current_value() + var expected_value: Variant = _extract_variadic_value(expected) + if not _validate_value_type(expected_value): + return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value)) + + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected_value, [], + expected_value, compare_mode)) + # find the difference + @warning_ignore("unsafe_cast") + var diffs := _array_div(compare_mode, current_value as Array[Variant], expected_value as Array[Variant], false) + var not_expect: Array[Variant] = diffs[0] + var not_found: Array[Variant] = diffs[1] + if not_expect.is_empty() and not_found.is_empty(): + return report_success() + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected_value, not_expect, + not_found, compare_mode)) + + +func _not_contains(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: + var current_value: Variant = get_current_value() + var expected_value: Variant = _extract_variadic_value(expected) + if not _validate_value_type(expected_value): + return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value)) + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected_value, [], + expected_value, compare_mode)) + @warning_ignore("unsafe_cast") + var diffs := _array_div(compare_mode, current_value as Array[Variant], expected_value as Array[Variant]) + var found: Array[Variant] = diffs[0] + @warning_ignore("unsafe_cast") + if found.size() == (current_value as Array).size(): + return report_success() + @warning_ignore("unsafe_cast") + var diffs2 := _array_div(compare_mode, expected_value as Array[Variant], diffs[1] as Array[Variant]) + return report_error(GdAssertMessages.error_arr_not_contains(current_value, expected_value, diffs2[0], compare_mode)) + + +func is_null() -> GdUnitArrayAssert: + @warning_ignore("return_value_discarded") + _base.is_null() + return self + + +func is_not_null() -> GdUnitArrayAssert: + @warning_ignore("return_value_discarded") + _base.is_not_null() + return self + + +func is_equal(...expected: Array) -> GdUnitArrayAssert: + var current_value: Variant = get_current_value() + var expected_value: Variant= _extract_variadic_value(expected) + if not _validate_value_type(expected_value): + return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value)) + if current_value == null and expected_value != null: + return report_error(GdAssertMessages.error_equal(null, expected_value)) + + if not _is_equal(current_value, expected_value): + var diff := _array_equals_div(current_value, expected_value) + var expected_as_list := GdArrayTools.as_string(diff[0], false) + var current_as_list := GdArrayTools.as_string(diff[1], false) + var index_report: Array = diff[2] + return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report)) + return report_success() + + +# Verifies that the current Array is equal to the given one, ignoring case considerations. +func is_equal_ignoring_case(...expected: Array) -> GdUnitArrayAssert: + var current_value: Variant = get_current_value() + var expected_value: Variant = _extract_variadic_value(expected) + if not _validate_value_type(expected_value): + return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value)) + if current_value == null and expected_value != null: + @warning_ignore("unsafe_cast") + return report_error(GdAssertMessages.error_equal(null, GdArrayTools.as_string(expected_value))) + + if not _is_equal(current_value, expected_value, true): + @warning_ignore("unsafe_cast") + var diff := _array_equals_div(current_value, expected_value, true) + var expected_as_list := GdArrayTools.as_string(diff[0]) + var current_as_list := GdArrayTools.as_string(diff[1]) + var index_report: Array = diff[2] + return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report)) + return report_success() + + +func is_not_equal(...expected: Array) -> GdUnitArrayAssert: + var current_value: Variant = get_current_value() + var expected_value: Variant = _extract_variadic_value(expected) + if not _validate_value_type(expected_value): + return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value)) + + if _is_equal(current_value, expected_value): + return report_error(GdAssertMessages.error_not_equal(current_value, expected_value)) + return report_success() + + +func is_not_equal_ignoring_case(...expected: Array) -> GdUnitArrayAssert: + var current_value: Variant = get_current_value() + var expected_value: Variant = _extract_variadic_value(expected) + if not _validate_value_type(expected_value): + return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value)) + + if _is_equal(current_value, expected_value, true): + @warning_ignore("unsafe_cast") + var c := GdArrayTools.as_string(current_value as Array) + @warning_ignore("unsafe_cast") + var e := GdArrayTools.as_string(expected_value) + return report_error(GdAssertMessages.error_not_equal_case_insensetiv(c, e)) + return report_success() + + +func is_empty() -> GdUnitArrayAssert: + var current_value: Variant = get_current_value() + @warning_ignore("unsafe_cast") + if current_value == null or (current_value as Array).size() > 0: + return report_error(GdAssertMessages.error_is_empty(current_value)) + return report_success() + + +func is_not_empty() -> GdUnitArrayAssert: + var current_value: Variant = get_current_value() + @warning_ignore("unsafe_cast") + if current_value != null and (current_value as Array).size() == 0: + return report_error(GdAssertMessages.error_is_not_empty()) + return report_success() + + +@warning_ignore("unused_parameter", "shadowed_global_identifier") +func is_same(expected: Variant) -> GdUnitArrayAssert: + if not _validate_value_type(expected): + return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) + var current: Variant = get_current_value() + if not is_same(current, expected): + @warning_ignore("return_value_discarded") + report_error(GdAssertMessages.error_is_same(current, expected)) + return self + + +func is_not_same(expected: Variant) -> GdUnitArrayAssert: + if not _validate_value_type(expected): + return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) + var current: Variant = get_current_value() + if is_same(current, expected): + @warning_ignore("return_value_discarded") + report_error(GdAssertMessages.error_not_same(current, expected)) + return self + + +func has_size(expected: int) -> GdUnitArrayAssert: + var current_value: Variant = get_current_value() + @warning_ignore("unsafe_cast") + if current_value == null or (current_value as Array).size() != expected: + return report_error(GdAssertMessages.error_has_size(current_value, expected)) + return report_success() + + +func contains(...expected: Array) -> GdUnitArrayAssert: + return _contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + + +func contains_exactly(...expected: Array) -> GdUnitArrayAssert: + return _contains_exactly(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + + +func contains_exactly_in_any_order(...expected: Array) -> GdUnitArrayAssert: + return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + + +func contains_same(...expected: Array) -> GdUnitArrayAssert: + return _contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) + + +func contains_same_exactly(...expected: Array) -> GdUnitArrayAssert: + return _contains_exactly(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) + + +func contains_same_exactly_in_any_order(...expected: Array) -> GdUnitArrayAssert: + return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) + + +func not_contains(...expected: Array) -> GdUnitArrayAssert: + return _not_contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + + +func not_contains_same(...expected: Array) -> GdUnitArrayAssert: + return _not_contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) + + +func is_instanceof(expected: Variant) -> GdUnitAssert: + @warning_ignore("unsafe_method_access") + _base.is_instanceof(expected) + return self + + +func extract(func_name: String, ...func_args: Array) -> GdUnitArrayAssert: + var extracted_elements := Array() + var args: Array = _extract_variadic_value(func_args) + var extractor := GdUnitFuncValueExtractor.new(func_name, args) + var current: Variant = get_current_value() + if current == null: + _current_value_provider = DefaultValueProvider.new(null) + else: + for element: Variant in current: + extracted_elements.append(extractor.extract_value(element)) + _current_value_provider = DefaultValueProvider.new(extracted_elements) + return self + + +func extractv(...extractors: Array) -> GdUnitArrayAssert: + var extracted_elements := Array() + var current: Variant = get_current_value() + if current == null: + _current_value_provider = DefaultValueProvider.new(null) + else: + for element: Variant in current: + var ev: Array[Variant] = [] + for index: int in extractors.size(): + var extractor: GdUnitValueExtractor = extractors[index] + ev.append(extractor.extract_value(element)) + if extractors.size() > 1: + extracted_elements.append(GdUnitTuple.new.callv(ev)) + else: + extracted_elements.append(ev[0]) + _current_value_provider = DefaultValueProvider.new(extracted_elements) + return self + + +## Small helper to support the old expected arguments as single array and variadic arguments +func _extract_variadic_value(values: Variant) -> Variant: + @warning_ignore("unsafe_method_access") + if values != null and values.size() == 1 and GdArrayTools.is_array_type(values[0]): + return values[0] + return values + + +@warning_ignore("incompatible_ternary") +func _is_equal( + left: Variant, + right: Variant, + case_sensitive := false, + compare_mode := GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool: + + @warning_ignore("unsafe_cast") + return GdObjects.equals( + (left as Array) if GdArrayTools.is_array_type(left) else left, + (right as Array) if GdArrayTools.is_array_type(right) else right, + case_sensitive, + compare_mode + ) + + +func _is_equals_sorted( + left: Variant, + right: Variant, + case_sensitive := false, + compare_mode := GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool: + + @warning_ignore("unsafe_cast") + return GdObjects.equals_sorted( + left as Array, + right as Array, + case_sensitive, + compare_mode) diff --git a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd.uid new file mode 100644 index 0000000..83db7a3 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://cugrq3kd6cfsd diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd new file mode 100644 index 0000000..9f578c4 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd @@ -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() diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd.uid new file mode 100644 index 0000000..2ca96f7 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://dwhamqtf0w80b diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd new file mode 100644 index 0000000..a5b53c1 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd @@ -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 diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd.uid b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd.uid new file mode 100644 index 0000000..87e7ca5 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd.uid @@ -0,0 +1 @@ +uid://cp756mi1gtrjl diff --git a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd new file mode 100644 index 0000000..2fc0ce4 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd @@ -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() diff --git a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd.uid new file mode 100644 index 0000000..7cecf08 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://dxdyrrpo58p5x diff --git a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd new file mode 100644 index 0000000..c57bbc2 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd @@ -0,0 +1,207 @@ +extends GdUnitDictionaryAssert + +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_DICTIONARY): + @warning_ignore("return_value_discarded") + report_error("GdUnitDictionaryAssert 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 report_success() -> GdUnitDictionaryAssert: + @warning_ignore("return_value_discarded") + _base.report_success() + return self + + +func report_error(error :String) -> GdUnitDictionaryAssert: + @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) -> GdUnitDictionaryAssert: + @warning_ignore("return_value_discarded") + _base.override_failure_message(message) + return self + + +func append_failure_message(message: String) -> GdUnitDictionaryAssert: + @warning_ignore("return_value_discarded") + _base.append_failure_message(message) + return self + + +func current_value() -> Variant: + return _base.current_value() + + +func is_null() -> GdUnitDictionaryAssert: + @warning_ignore("return_value_discarded") + _base.is_null() + return self + + +func is_not_null() -> GdUnitDictionaryAssert: + @warning_ignore("return_value_discarded") + _base.is_not_null() + return self + + +func is_equal(expected: Variant) -> GdUnitDictionaryAssert: + var current :Variant = current_value() + if current == null: + return report_error(GdAssertMessages.error_equal(null, GdAssertMessages.format_dict(expected))) + if not GdObjects.equals(current, expected): + var c := GdAssertMessages.format_dict(current) + var e := GdAssertMessages.format_dict(expected) + return report_error(GdAssertMessages.error_equal(c, e)) + return report_success() + + +func is_not_equal(expected: Variant) -> GdUnitDictionaryAssert: + var current: Variant = current_value() + if GdObjects.equals(current, expected): + return report_error(GdAssertMessages.error_not_equal(current, expected)) + return report_success() + + +@warning_ignore("unused_parameter", "shadowed_global_identifier") +func is_same(expected :Variant) -> GdUnitDictionaryAssert: + var current :Variant = current_value() + if current == null: + return report_error(GdAssertMessages.error_equal(null, GdAssertMessages.format_dict(expected))) + if not is_same(current, expected): + var c := GdAssertMessages.format_dict(current) + var e := GdAssertMessages.format_dict(expected) + return report_error(GdAssertMessages.error_is_same(c, e)) + return report_success() + + +@warning_ignore("unused_parameter", "shadowed_global_identifier") +func is_not_same(expected :Variant) -> GdUnitDictionaryAssert: + var current :Variant = current_value() + if is_same(current, expected): + return report_error(GdAssertMessages.error_not_same(current, expected)) + return report_success() + + +func is_empty() -> GdUnitDictionaryAssert: + var current :Variant = current_value() + @warning_ignore("unsafe_cast") + if current == null or not (current as Dictionary).is_empty(): + return report_error(GdAssertMessages.error_is_empty(current)) + return report_success() + + +func is_not_empty() -> GdUnitDictionaryAssert: + var current :Variant = current_value() + @warning_ignore("unsafe_cast") + if current == null or (current as Dictionary).is_empty(): + return report_error(GdAssertMessages.error_is_not_empty()) + return report_success() + + +func has_size(expected: int) -> GdUnitDictionaryAssert: + var current :Variant = current_value() + if current == null: + return report_error(GdAssertMessages.error_is_not_null()) + @warning_ignore("unsafe_cast") + if (current as Dictionary).size() != expected: + return report_error(GdAssertMessages.error_has_size(current, expected)) + return report_success() + + +func _contains_keys(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: + var current :Variant = current_value() + var expected_value: Array = _extract_variadic_value(expected) + if current == null: + return report_error(GdAssertMessages.error_is_not_null()) + # find expected keys + @warning_ignore("unsafe_cast") + var keys_not_found :Array = expected_value.filter(_filter_by_key.bind((current as Dictionary).keys(), compare_mode)) + if not keys_not_found.is_empty(): + @warning_ignore("unsafe_cast") + return report_error(GdAssertMessages.error_contains_keys((current as Dictionary).keys() as Array, expected_value, + keys_not_found, compare_mode)) + return report_success() + + +func _contains_key_value(key :Variant, value :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: + var current :Variant = current_value() + var expected := [key] + if current == null: + return report_error(GdAssertMessages.error_is_not_null()) + var dict_current: Dictionary = current + var keys_not_found :Array = expected.filter(_filter_by_key.bind(dict_current.keys(), compare_mode)) + if not keys_not_found.is_empty(): + return report_error(GdAssertMessages.error_contains_keys(dict_current.keys() as Array, expected, keys_not_found, compare_mode)) + if not GdObjects.equals(dict_current[key], value, false, compare_mode): + return report_error(GdAssertMessages.error_contains_key_value(key, value, dict_current[key], compare_mode)) + return report_success() + + +func _not_contains_keys(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: + var current :Variant = current_value() + var expected_value: Array = _extract_variadic_value(expected) + if current == null: + return report_error(GdAssertMessages.error_is_not_null()) + var dict_current: Dictionary = current + var keys_found :Array = dict_current.keys().filter(_filter_by_key.bind(expected_value, compare_mode, true)) + if not keys_found.is_empty(): + return report_error(GdAssertMessages.error_not_contains_keys(dict_current.keys() as Array, expected_value, keys_found, compare_mode)) + return report_success() + + +func contains_keys(...expected: Array) -> GdUnitDictionaryAssert: + return _contains_keys(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + + +func contains_key_value(key :Variant, value :Variant) -> GdUnitDictionaryAssert: + return _contains_key_value(key, value, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + + +func not_contains_keys(...expected: Array) -> GdUnitDictionaryAssert: + return _not_contains_keys(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + + +func contains_same_keys(expected :Array) -> GdUnitDictionaryAssert: + return _contains_keys(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) + + +func contains_same_key_value(key :Variant, value :Variant) -> GdUnitDictionaryAssert: + return _contains_key_value(key, value, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) + + +func not_contains_same_keys(...expected: Array) -> GdUnitDictionaryAssert: + return _not_contains_keys(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) + + +func _filter_by_key(element :Variant, values :Array, compare_mode :GdObjects.COMPARE_MODE, is_not :bool = false) -> bool: + for key :Variant in values: + if GdObjects.equals(key, element, false, compare_mode): + return is_not + return !is_not + + +## Small helper to support the old expected arguments as single array and variadic arguments +func _extract_variadic_value(values: Variant) -> Variant: + @warning_ignore("unsafe_method_access") + if values != null and values.size() == 1 and GdArrayTools.is_array_type(values[0]): + return values[0] + return values diff --git a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd.uid new file mode 100644 index 0000000..4ba626d --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://cxrjs0mlg1qr0 diff --git a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd new file mode 100644 index 0000000..198624c --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd @@ -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 diff --git a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd.uid new file mode 100644 index 0000000..cf78a00 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://cr53a1nnhmaqa diff --git a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd new file mode 100644 index 0000000..c4f9570 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd @@ -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 diff --git a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd.uid new file mode 100644 index 0000000..b8360b7 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://mr5x0e3q4g0a diff --git a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd new file mode 100644 index 0000000..83d7e05 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd @@ -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() diff --git a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd.uid new file mode 100644 index 0000000..3d0dc07 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://cj7rei3qtlurx diff --git a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd new file mode 100644 index 0000000..c38acf0 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd @@ -0,0 +1,177 @@ +extends GdUnitFuncAssert + + +const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") +const DEFAULT_TIMEOUT := 2000 + + +var _current_value_provider :ValueProvider +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 +var _sleep_timer :Timer = null + + +func _init(instance :Object, func_name :String, args := Array()) -> void: + _line_number = GdUnitAssertions.get_line_number() + GdAssertReports.reset_last_error_line_number() + # save the actual assert instance on the current thread context + GdUnitThreadManager.get_current_context().set_assert(self) + # verify at first the function name exists + if not instance.has_method(func_name): + @warning_ignore("return_value_discarded") + report_error("The function '%s' do not exists checked instance '%s'." % [func_name, instance]) + _interrupted = true + else: + _current_value_provider = CallBackValueProvider.new(instance, func_name, args) + + +func _notification(what :int) -> void: + if what == NOTIFICATION_PREDELETE: + _interrupted = true + var main_node :Node = (Engine.get_main_loop() as SceneTree).root + if is_instance_valid(_current_value_provider): + _current_value_provider.dispose() + _current_value_provider = null + if is_instance_valid(_sleep_timer): + _sleep_timer.set_wait_time(0.0001) + _sleep_timer.stop() + main_node.remove_child(_sleep_timer) + _sleep_timer.free() + _sleep_timer = null + + +func report_success() -> GdUnitFuncAssert: + GdAssertReports.report_success() + return self + + +func report_error(failure :String) -> GdUnitFuncAssert: + _current_failure_message = GdAssertMessages.build_failure_message(failure, _additional_failure_message, _custom_failure_message) + GdAssertReports.report_error(_current_failure_message, _line_number) + return self + + +func failure_message() -> String: + return _current_failure_message + + +func override_failure_message(message: String) -> GdUnitFuncAssert: + _custom_failure_message = message + return self + + +func append_failure_message(message: String) -> GdUnitFuncAssert: + _additional_failure_message = message + return self + + +func wait_until(timeout := 2000) -> GdUnitFuncAssert: + if timeout <= 0: + push_warning("Invalid timeout param, alloed timeouts must be grater than 0. Use default timeout instead") + _timeout = DEFAULT_TIMEOUT + else: + _timeout = timeout + return self + + +func is_null() -> GdUnitFuncAssert: + await _validate_callback(cb_is_null) + return self + + +func is_not_null() -> GdUnitFuncAssert: + await _validate_callback(cb_is_not_null) + return self + + +func is_false() -> GdUnitFuncAssert: + await _validate_callback(cb_is_false) + return self + + +func is_true() -> GdUnitFuncAssert: + await _validate_callback(cb_is_true) + return self + + +func is_equal(expected: Variant) -> GdUnitFuncAssert: + await _validate_callback(cb_is_equal, expected) + return self + + +func is_not_equal(expected: Variant) -> GdUnitFuncAssert: + await _validate_callback(cb_is_not_equal, expected) + return self + + +# we need actually to define this Callable as functions otherwise we results into leaked scripts here +# this is actually a Godot bug and needs this kind of workaround +func cb_is_null(c :Variant, _e :Variant) -> bool: return c == null +func cb_is_not_null(c :Variant, _e :Variant) -> bool: return c != null +func cb_is_false(c :Variant, _e :Variant) -> bool: return c == false +func cb_is_true(c :Variant, _e :Variant) -> bool: return c == true +func cb_is_equal(c :Variant, e :Variant) -> bool: return GdObjects.equals(c,e) +func cb_is_not_equal(c :Variant, e :Variant) -> bool: return not GdObjects.equals(c, e) + + +func do_interrupt() -> void: + _interrupted = true + + +func _validate_callback(predicate :Callable, expected :Variant = null) -> void: + if _interrupted: + return + GdUnitMemoryObserver.guard_instance(self) + var time_scale := Engine.get_time_scale() + var timer := Timer.new() + timer.set_name("gdunit_funcassert_interrupt_timer_%d" % timer.get_instance_id()) + var scene_tree := Engine.get_main_loop() as SceneTree + scene_tree.root.add_child(timer) + timer.add_to_group("GdUnitTimers") + @warning_ignore("return_value_discarded") + timer.timeout.connect(do_interrupt, CONNECT_DEFERRED) + timer.set_one_shot(true) + timer.start((_timeout/1000.0)*time_scale) + _sleep_timer = Timer.new() + _sleep_timer.set_name("gdunit_funcassert_sleep_timer_%d" % _sleep_timer.get_instance_id() ) + scene_tree.root.add_child(_sleep_timer) + + while true: + var current :Variant = await next_current_value() + # is interupted or predicate success + if _interrupted or predicate.call(current, expected): + break + if is_instance_valid(_sleep_timer): + _sleep_timer.start(0.05) + await _sleep_timer.timeout + + _sleep_timer.stop() + await scene_tree.process_frame + if _interrupted: + # https://github.com/godotengine/godot/issues/73052 + #var predicate_name = predicate.get_method() + var predicate_name :String = str(predicate).split('::')[1] + @warning_ignore("return_value_discarded") + report_error(GdAssertMessages.error_interrupted( + predicate_name.strip_edges().trim_prefix("cb_"), + expected, + LocalTime.elapsed(_timeout) + ) + ) + else: + @warning_ignore("return_value_discarded") + report_success() + _sleep_timer.free() + timer.free() + GdUnitMemoryObserver.unguard_instance(self) + + +func next_current_value() -> Variant: + @warning_ignore("redundant_await") + if is_instance_valid(_current_value_provider): + return await _current_value_provider.get_value() + return "invalid value" diff --git a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd.uid new file mode 100644 index 0000000..b10c045 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://cd337pgd74ed0 diff --git a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd new file mode 100644 index 0000000..ce5503d --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd @@ -0,0 +1,151 @@ +extends GdUnitGodotErrorAssert + +var _current_failure_message := "" +var _custom_failure_message := "" +var _additional_failure_message := "" +var _callable: Callable +var _logger := GodotGdErrorMonitor.GdUnitLogger.new(true, true) + + +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]: + await _callable.call() + return _logger.entries() + + +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): + GdUnitThreadManager.get_current_context().get_execution_context().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" + + var values := [] + for entry in log_entries: + values.append(entry) + return "\n".join(values) + + +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: + if not _validate_callable(): + return self + + 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: + if not _validate_callable(): + return self + + 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. + expected: '%s' + current: '%s' + """.dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)]) + + +func is_push_warning(expected_warning: Variant) -> GdUnitGodotErrorAssert: + if not _validate_callable(): + return self + + 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. + expected: '%s' + current: '%s' + """.dedent().trim_prefix("\n") % [expected_warning, _to_list(log_entries)]) + + +func is_push_error(expected_error: Variant) -> GdUnitGodotErrorAssert: + if not _validate_callable(): + return self + + 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. + expected: '%s' + current: '%s' + """.dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)]) + + +func _validate_callable() -> bool: + if _callable == null or not _callable.is_valid(): + @warning_ignore("return_value_discarded") + _report_error("Invalid Callable '%s'" % _callable) + return false + return true diff --git a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd.uid new file mode 100644 index 0000000..8ecadb7 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://rkf7jw6mw4d4 diff --git a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd new file mode 100644 index 0000000..bdee249 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd @@ -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() diff --git a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd.uid new file mode 100644 index 0000000..512cc48 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://dbms8smcdukg0 diff --git a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd new file mode 100644 index 0000000..955e1ef --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd @@ -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() diff --git a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd.uid new file mode 100644 index 0000000..349ff7b --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://biqm7ejvhb4oc diff --git a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd new file mode 100644 index 0000000..98a6768 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd @@ -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() diff --git a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd.uid new file mode 100644 index 0000000..6ba023e --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://bpeci78q50dtq diff --git a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd new file mode 100644 index 0000000..0da696a --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd @@ -0,0 +1,172 @@ +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_or_name: Variant) -> GdUnitSignalAssert: + if not (signal_or_name is String or signal_or_name is Signal): + return report_error("Invalid signal_name: expected String or Signal, but is '%s'" % type_string(typeof(signal_or_name))) + + var signal_name := _to_signal_name(signal_or_name) + + 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(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert: + _line_number = GdUnitAssertions.get_line_number() + @warning_ignore("unsafe_call_argument") + return await _wail_until_signal( + signal_name, + _wrap_arguments.callv(signal_args), + false) + + +# Verifies that given signal is NOT emitted until waiting time +func is_not_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert: + _line_number = GdUnitAssertions.get_line_number() + @warning_ignore("unsafe_call_argument") + return await _wail_until_signal( + signal_name, + _wrap_arguments.callv(signal_args), + true) + + +func _wrap_arguments(...args: Array) -> Array: + # Check using old syntax + if not args.is_empty() and args[0] is Array: + return args[0] + return args + + +func _wail_until_signal(signal_or_name: Variant, expected_args: Array, expect_not_emitted: bool) -> GdUnitSignalAssert: + if _emitter == null: + return report_error("Can't wait for signal checked a NULL object.") + if not (signal_or_name is String or signal_or_name is Signal): + return report_error("Invalid signal_name: expected String or Signal, but is '%s'" % type_string(typeof(signal_or_name))) + + var signal_name := _to_signal_name(signal_or_name) + # 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 + + +func _to_signal_name(signal_or_name: Variant) -> String: + @warning_ignore("unsafe_cast") + return (signal_or_name as Signal).get_name() if signal_or_name is Signal else signal_or_name diff --git a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd.uid new file mode 100644 index 0000000..3690588 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://n33x4jg0yjqh diff --git a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd new file mode 100644 index 0000000..cdbcdff --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd @@ -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 diff --git a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd.uid new file mode 100644 index 0000000..d257a25 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://cp0rvxafx8aqw diff --git a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd new file mode 100644 index 0000000..fbc031a --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd @@ -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() diff --git a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd.uid b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd.uid new file mode 100644 index 0000000..4f08785 --- /dev/null +++ b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd.uid @@ -0,0 +1 @@ +uid://baa45s62kgmhi diff --git a/addons/gdUnit4/src/asserts/ValueProvider.gd b/addons/gdUnit4/src/asserts/ValueProvider.gd new file mode 100644 index 0000000..be01f70 --- /dev/null +++ b/addons/gdUnit4/src/asserts/ValueProvider.gd @@ -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 diff --git a/addons/gdUnit4/src/asserts/ValueProvider.gd.uid b/addons/gdUnit4/src/asserts/ValueProvider.gd.uid new file mode 100644 index 0000000..f9e818d --- /dev/null +++ b/addons/gdUnit4/src/asserts/ValueProvider.gd.uid @@ -0,0 +1 @@ +uid://fh2vdv250867 |
