Fresh start: replace with naxIO/netfox-cs-sample foundation
Complete replacement of the tactical-shooter project with the netfox-cs-sample (MIT) — a CS 1.6 inspired multiplayer FPS built with Godot 4 and netfox. ## What's new - Full CS-style gameplay: teams (T/CT), rounds, economy, buy menu - 6 weapons: Knife, Glock, USP, AK-47, M4A1, AWP - Bomb plant/defuse with 2 bombsites - Flashbang & smoke grenades - Proper netfox rollback netcode at 64 tick - Network popup UI for host/join - HUD, crosshair, round timer, scoreboard - All netfox singletons registered as autoloads (works in exported builds) ## Architecture - Listen-server (host from client, no dedicated server binary) - Multiplayer-fps game lives at examples/multiplayer-fps/ - Netfox addons registered as autoloads for exported build compat - Godot 4.7 with Forward+ renderer ## Removed - Old headless-server architecture (client_main, server_main, player.gd, etc.) - Custom netfox bootstrap with ENet fallback - Old ChaffGames FPS template (2,420 lines, 844 KB) - SimulationServer GDExtension stub - Godot-jolt physics (netfox sample uses default Godot physics) - Duplicate weapon_data.gd, anti_cheat.gd, round_manager.gd, etc. - Server browser API Python venv (87 MB) - test_range map and modular assets ## Preserved - Git history - Server config at config/default_server_config.cfg - Windows export preset - Build directory (gitignored) Co-authored-by: naxIO <naxIO@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
extends VestTestMixin
|
||||
|
||||
func assert_that(value: Variant) -> _Assertion:
|
||||
return _Assertion.new(self, value)
|
||||
|
||||
class _Assertion:
|
||||
var _test: VestTest
|
||||
var _value: Variant
|
||||
|
||||
func _init(p_test: Variant, p_value: Variant):
|
||||
assert(p_test is VestTest, "assert_that mixin used from outside of VestTest!")
|
||||
|
||||
_test = p_test
|
||||
_value = p_value
|
||||
|
||||
## Expect a [param condition] about the asserted value to be true.
|
||||
func passes(condition: Callable, p_message: String = "") -> _Assertion:
|
||||
_test.expect(condition.call(_value), p_message)
|
||||
return self
|
||||
|
||||
## Expect a [param condition] about the asserted value to be false.
|
||||
func fails(condition: Callable, p_message: String = "") -> _Assertion:
|
||||
_test.expect_not(condition.call(_value), p_message)
|
||||
return self
|
||||
|
||||
## Expect the asserted value to be equal to [param expected].
|
||||
## [br][br]
|
||||
## If [param actual] has an [code]equals()[/code] method, it will be used.
|
||||
func is_equal_to(expected: Variant, p_message: String = "Actual value differs from expected!") -> _Assertion:
|
||||
_test.expect_equal(_value, expected, p_message)
|
||||
return self
|
||||
|
||||
## Expect the asserted value to not be equal to [param expected].
|
||||
## [br][br]
|
||||
## If [param actual] has an [code]equals()[/code] method, it will be used.
|
||||
func is_not_equal_to(expected: Variant, p_message: String = "Actual value equals expected!") -> _Assertion:
|
||||
_test.expect_not_equal(_value, expected, p_message)
|
||||
return self
|
||||
|
||||
## Expect the asserted value to be empty.
|
||||
## [br][br]
|
||||
## If it's a custom type implementing [code]is_empty()[/code], that method will
|
||||
## be used.
|
||||
func is_empty(p_message: String = "Object was not empty!") -> _Assertion:
|
||||
_test.expect_empty(_value, p_message)
|
||||
return self
|
||||
|
||||
## Expect the asserted value to not be empty.
|
||||
## [br][br]
|
||||
## If it's a custom type implementing [code]is_empty()[/code], that method will
|
||||
## be used.
|
||||
func is_not_empty(p_message: String = "Object was empty!") -> _Assertion:
|
||||
_test.expect_not_empty(_value, p_message)
|
||||
return self
|
||||
|
||||
## Expect the asserted value to contain [param item].
|
||||
## [br][br]
|
||||
## If it's a custom type implementing [code]has()[/code], that method will be
|
||||
## used.
|
||||
func contains(item: Variant, p_message: String = "Item is missing from collection!") -> _Assertion:
|
||||
_test.expect_contains(_value, item, p_message)
|
||||
return self
|
||||
|
||||
## Expect the asserted value to not contain [param item].
|
||||
## [br][br]
|
||||
## If it's a custom type implementing [code]has()[/code], that method will be
|
||||
## used.
|
||||
func does_not_contain(item: Variant, p_message: String = "Item is in collection!") -> _Assertion:
|
||||
_test.expect_does_not_contain(_value, item, p_message)
|
||||
return self
|
||||
|
||||
## Expect the asserted value to be null.
|
||||
func is_null(p_message: String = "Item is not null!") -> _Assertion:
|
||||
_test.expect_null(_value, p_message)
|
||||
return self
|
||||
|
||||
## Expect the asserted value to not be null.
|
||||
func is_not_null(p_message: String = "Item is null!") -> _Assertion:
|
||||
_test.expect_not_null(_value, p_message)
|
||||
return self
|
||||
@@ -0,0 +1 @@
|
||||
uid://djf35joyl6rwr
|
||||
@@ -0,0 +1,79 @@
|
||||
extends VestTestMixin
|
||||
|
||||
## Mixin for capturing and asserting signals
|
||||
##
|
||||
## @tutorial(Capturing signals): https://foxssake.github.io/vest/latest/user-guide/capturing-signals/
|
||||
|
||||
# Maps signals to an array of recorded emissions
|
||||
# Each array item is an individual array containing the emission params
|
||||
var _signal_captures := {}
|
||||
|
||||
# Array of [signal, recorder, is_persistent] tuples
|
||||
var _signal_recorders: Array[Array] = []
|
||||
|
||||
## Capture all emissions of a signal.
|
||||
## [br][br]
|
||||
## Mark the capture as [param persistent] if you want the capture to persist
|
||||
## between test cases.
|
||||
func capture_signal(what: Signal, arg_count: int = 0, persistent: bool = false):
|
||||
# Reset captures
|
||||
_signal_captures[what] = []
|
||||
|
||||
# Add listener
|
||||
var recorder = _get_signal_recorder(what, arg_count)
|
||||
if not recorder:
|
||||
push_warning("Can't capture signal with %d arguments!" % arg_count)
|
||||
return
|
||||
|
||||
what.connect(recorder)
|
||||
_signal_recorders.append([what, recorder, persistent])
|
||||
|
||||
## Get the captured signal emissions for a given signal.
|
||||
## [br][br]
|
||||
## Note that the captured emissions are reset between test cases. [br]
|
||||
## Returns an array of signal emission parameters for each captured emission.
|
||||
func get_signal_emissions(what: Signal) -> Array:
|
||||
return _signal_captures.get(what, [])
|
||||
|
||||
func _init():
|
||||
super()
|
||||
on_case_begin.connect(func(__):
|
||||
# Remove non-persistent recorders
|
||||
_cleanup_recorders()
|
||||
|
||||
# Clear captures
|
||||
_signal_captures.clear()
|
||||
)
|
||||
|
||||
func _cleanup_recorders():
|
||||
var filtered_recorders: Array[Array] = []
|
||||
|
||||
for recorder_tuple in _signal_recorders:
|
||||
var recorded_signal := recorder_tuple[0] as Signal
|
||||
var recorder := recorder_tuple[1] as Callable
|
||||
var persistent := recorder_tuple[2] as bool
|
||||
|
||||
if persistent:
|
||||
filtered_recorders.append(recorder_tuple)
|
||||
else:
|
||||
recorded_signal.disconnect(recorder)
|
||||
|
||||
_signal_recorders = filtered_recorders
|
||||
|
||||
func _get_signal_recorder(what: Signal, arg_count: int):
|
||||
match(arg_count):
|
||||
0: return func(): _record_emission(what, [])
|
||||
1: return func(a1): _record_emission(what, [a1])
|
||||
2: return func(a1, a2): _record_emission(what, [a1, a2])
|
||||
3: return func(a1, a2, a3): _record_emission(what, [a1, a2, a3])
|
||||
4: return func(a1, a2, a3, a4): _record_emission(what, [a1, a2, a3, a4])
|
||||
5: return func(a1, a2, a3, a4, a5): _record_emission(what, [a1, a2, a3, a4, a5])
|
||||
6: return func(a1, a2, a3, a4, a5, a6): _record_emission(what, [a1, a2, a3, a4, a5, a6])
|
||||
7: return func(a1, a2, a3, a4, a5, a6, a7): _record_emission(what, [a1, a2, a3, a4, a5, a6, a7])
|
||||
8: return func(a1, a2, a3, a4, a5, a6, a7, a8): _record_emission(what, [a1, a2, a3, a4, a5, a6, a7, a8])
|
||||
_: return null
|
||||
|
||||
func _record_emission(what: Signal, args: Array):
|
||||
if not _signal_captures.has(what):
|
||||
_signal_captures[what] = []
|
||||
_signal_captures[what].append(args)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b7ik7rpue0oiy
|
||||
@@ -0,0 +1,124 @@
|
||||
extends VestTestMixin
|
||||
|
||||
## Mixin for asserting test requirement
|
||||
##
|
||||
## @tutorial(Assertions): https://foxssake.github.io/vest/latest/user-guide/assertions/
|
||||
|
||||
## Expect a [param condition] to be true.
|
||||
func expect(condition: bool, p_message: String = "") -> void:
|
||||
if condition:
|
||||
ok()
|
||||
else:
|
||||
fail(p_message)
|
||||
|
||||
## Expect a [param condition] to be false.
|
||||
func expect_not(condition: bool, p_message: String = "") -> void:
|
||||
if not condition:
|
||||
ok()
|
||||
else:
|
||||
fail(p_message)
|
||||
|
||||
## Expect two values to be equal.
|
||||
## [br][br]
|
||||
## If [param actual] has an [code]equals()[/code] method, it will be used.
|
||||
func expect_equal(actual: Variant, expected: Variant, p_message: String = "Actual value differs from expected!") -> void:
|
||||
if Vest.__.Matchers.is_equal(actual, expected):
|
||||
ok()
|
||||
else:
|
||||
fail(p_message, { "expect": expected, "got": actual })
|
||||
|
||||
## Expect two values not to be equal.
|
||||
## [br][br]
|
||||
## If [param actual] has an [code]equals()[/code] method, it will be used.
|
||||
func expect_not_equal(actual: Variant, expected: Variant, p_message: String = "Actual value equals expected!") -> void:
|
||||
if Vest.__.Matchers.is_equal(actual, expected):
|
||||
fail(p_message, { "expect": expected, "got": actual })
|
||||
else:
|
||||
ok()
|
||||
|
||||
## Expect a [param condition] to be true.
|
||||
## [br][br]
|
||||
## Synonim of [method expect], aimed at better readability for asserting bools.
|
||||
func expect_true(condition: bool, p_message: String = "") -> void:
|
||||
expect(condition, p_message)
|
||||
|
||||
## Expect a [param condition] to be false.
|
||||
## [br][br]
|
||||
## Synonim of [method expect_not], aimed at better readability for asserting
|
||||
## bools.
|
||||
func expect_false(condition: bool, p_message: String = "") -> void:
|
||||
expect_not(condition, p_message)
|
||||
|
||||
## Expect an [param object] to be empty.
|
||||
## [br][br]
|
||||
## If it's a custom type implementing [code]is_empty()[/code], that method will
|
||||
## be used.
|
||||
func expect_empty(object: Variant, p_message: String = "Object was not empty!") -> void:
|
||||
match Vest.__.Matchers.is_empty(object):
|
||||
true:
|
||||
ok()
|
||||
false:
|
||||
fail(p_message)
|
||||
ERR_METHOD_NOT_FOUND:
|
||||
fail("Object has no is_empty() method!", { "object": object })
|
||||
ERR_CANT_RESOLVE:
|
||||
fail("Unknown object, can't be checked for emptiness!", { "object": object })
|
||||
|
||||
## Expect an [param object] to not be empty.
|
||||
## [br][br]
|
||||
## If it's a custom type implementing [code]is_empty()[/code], that method will
|
||||
## be used.
|
||||
func expect_not_empty(object: Variant, p_message: String = "Object was empty!") -> void:
|
||||
match Vest.__.Matchers.is_empty(object):
|
||||
true:
|
||||
fail(p_message)
|
||||
false:
|
||||
ok()
|
||||
ERR_METHOD_NOT_FOUND:
|
||||
fail("Object has no is_empty() method!", { "object": object })
|
||||
ERR_CANT_RESOLVE:
|
||||
fail("Unknown object, can't be checked for emptiness!", { "object": object })
|
||||
|
||||
## Expect an [param object] to contain [param item].
|
||||
## [br][br]
|
||||
## If it's a custom type implementing [code]has()[/code], that method will be
|
||||
## used.
|
||||
func expect_contains(object: Variant, item: Variant, p_message: String = "Item is missing from collection!") -> void:
|
||||
match Vest.__.Matchers.contains(object, item):
|
||||
true:
|
||||
ok()
|
||||
false:
|
||||
fail(p_message, { "got": object, "missing": item })
|
||||
ERR_METHOD_NOT_FOUND:
|
||||
fail("Object has no has() method!", { "object": object })
|
||||
ERR_CANT_RESOLVE:
|
||||
fail("Unknown object, can't be checked if it contains item!", { "object": object })
|
||||
|
||||
## Expect an [param object] to not contain [param item].
|
||||
## [br][br]
|
||||
## If it's a custom type implementing [code]has()[/code], that method will be
|
||||
## used.
|
||||
func expect_does_not_contain(object: Variant, item: Variant, p_message: String = "Item is in collection!") -> void:
|
||||
match Vest.__.Matchers.contains(object, item):
|
||||
true:
|
||||
fail(p_message, { "got": object, "excess": item })
|
||||
false:
|
||||
ok()
|
||||
ERR_METHOD_NOT_FOUND:
|
||||
fail("Object has no has() method!", { "object": object })
|
||||
ERR_CANT_RESOLVE:
|
||||
fail("Unknown object, can't be checked if it contains item!", { "object": object })
|
||||
|
||||
## Expect a [param value] to be null.
|
||||
func expect_null(value: Variant, p_message: String = "Item is not null!") -> void:
|
||||
if value == null:
|
||||
ok()
|
||||
else:
|
||||
fail(p_message, { "got": value })
|
||||
|
||||
## Expect a [param value] to not be null.
|
||||
func expect_not_null(value: Variant, p_message: String = "Item is null!") -> void:
|
||||
if value != null:
|
||||
ok()
|
||||
else:
|
||||
fail(p_message)
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0iygb4e10hrg
|
||||
@@ -0,0 +1,95 @@
|
||||
extends VestTestMixin
|
||||
|
||||
# Automatically gathers tests defined in the test class
|
||||
|
||||
func _get_ignored_methods() -> Array[String]:
|
||||
return [
|
||||
"get_suite_name",
|
||||
"before_suite", "before_case", "before_benchmark", "before_each",
|
||||
"after_suite", "after_case", "after_benchmark", "after_each"
|
||||
]
|
||||
|
||||
func _get_suite_name() -> String:
|
||||
# Check if callback is implemented
|
||||
if has_method("get_suite_name"):
|
||||
return call("get_suite_name")
|
||||
|
||||
# Check if class_name is set
|
||||
var script := get_script() as Script
|
||||
var pattern := RegEx.create_from_string("class_name\\s+([^\n\\s]+)")
|
||||
var hit := pattern.search(script.source_code)
|
||||
|
||||
if hit:
|
||||
return hit.get_string(1)
|
||||
|
||||
# Fall back to script path
|
||||
return script.resource_path
|
||||
|
||||
func _get_suite() -> VestDefs.Suite:
|
||||
var script := get_script() as Script
|
||||
var ignored_methods := _get_ignored_methods()
|
||||
var inherited_methods := (script.get_base_script()
|
||||
.get_script_method_list()
|
||||
.map(func(it): return it["name"])
|
||||
)
|
||||
|
||||
var methods := (script.get_script_method_list()
|
||||
.filter(func(it): return not it["name"].begins_with("_"))
|
||||
.filter(func(it): return not inherited_methods.has(it["name"]))
|
||||
.filter(func(it): return not ignored_methods.has(it["name"]))
|
||||
)
|
||||
|
||||
var define_methods: Array[Dictionary] = []
|
||||
var case_methods: Array[Dictionary] = []
|
||||
var parametric_methods: Array[Dictionary] = []
|
||||
|
||||
for method in methods:
|
||||
if method["name"].begins_with("suite"):
|
||||
define_methods.append(method)
|
||||
elif method["name"].begins_with("test") and not method["default_args"].is_empty() and method["default_args"][0] is String:
|
||||
parametric_methods.append(method)
|
||||
elif method["name"].begins_with("test"):
|
||||
case_methods.append(method)
|
||||
|
||||
return await define(_get_suite_name(), func():
|
||||
for method in define_methods:
|
||||
await call(method["name"])
|
||||
|
||||
for method in case_methods:
|
||||
var method_name := method["name"] as String
|
||||
var test_name := method_name.trim_prefix("test").trim_suffix("__only").capitalize()
|
||||
var callback := func(): await call(method["name"])
|
||||
var is_only := _is_only(method_name)
|
||||
|
||||
test(test_name, callback, is_only, method_name)
|
||||
|
||||
for method in parametric_methods:
|
||||
var method_name := method["name"] as String
|
||||
var test_name := method_name.trim_prefix("test").trim_suffix("__only").capitalize()
|
||||
var is_only := _is_only(method_name)
|
||||
|
||||
var param_provider_name := method["default_args"][0] as String
|
||||
if not has_method(param_provider_name):
|
||||
push_warning(
|
||||
"Can't run parametrized test \"%s\", provider method \"%s\" is missing!" % \
|
||||
[method["name"], param_provider_name]
|
||||
)
|
||||
|
||||
var params = await call(param_provider_name)
|
||||
if not params is Array or not params.all(func(it): return it is Array):
|
||||
push_warning(
|
||||
"Can't run parametrized test \"%s\", provider \"%s\" didn't return array or arrays: %s" % \
|
||||
[method["name"], param_provider_name, params]
|
||||
)
|
||||
|
||||
for i in range(params.size()):
|
||||
test(
|
||||
"%s#%d %s" % [test_name, i+1, params[i]],
|
||||
func(): await callv(method["name"], params[i]),
|
||||
is_only,
|
||||
method_name
|
||||
)
|
||||
)
|
||||
|
||||
func _is_only(name: String) -> bool:
|
||||
return name.ends_with("__only")
|
||||
@@ -0,0 +1 @@
|
||||
uid://b7g760psoly27
|
||||
@@ -0,0 +1,13 @@
|
||||
extends VestTestMixin
|
||||
|
||||
# Manages logs added using [method Vest.message]
|
||||
|
||||
func _init():
|
||||
super()
|
||||
|
||||
on_case_finish.connect(func(_case):
|
||||
var messages := Vest._get_messages()
|
||||
if not messages.is_empty():
|
||||
_result.data["messages"] = messages
|
||||
Vest._clear_messages()
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://hkri0vr7706v
|
||||
@@ -0,0 +1,81 @@
|
||||
extends VestTestMixin
|
||||
|
||||
## Provides mocking for tests
|
||||
##
|
||||
## @tutorial(Mocks): https://foxssake.github.io/vest/latest/user-guide/mocks/
|
||||
|
||||
# TODO: Fail test case if there's unhandled calls
|
||||
|
||||
var _mock_generator := VestMockGenerator.new()
|
||||
var _mock_handler := VestMockHandler.new()
|
||||
|
||||
# Maps Scripts to their mocked counterparts
|
||||
var _mock_script_cache := {}
|
||||
|
||||
## Create a mocked instance of a given script
|
||||
func mock(script: Script):
|
||||
var mocked_script := _get_mock_script(script)
|
||||
var mocked_object = mocked_script.new()
|
||||
|
||||
_mock_handler.take_over(mocked_object)
|
||||
return mocked_object
|
||||
|
||||
## Get calls of a mock object's method
|
||||
func get_calls_of(method: Callable) -> Array[Array]:
|
||||
var result: Array[Array] = []
|
||||
|
||||
for call_data in _mock_handler.get_calls():
|
||||
if call_data.method != method:
|
||||
continue
|
||||
result.append(call_data.args)
|
||||
|
||||
return result
|
||||
|
||||
## Start specifying an answer for a mocked method call
|
||||
func when(method: Callable) -> AnswerBuilder:
|
||||
return AnswerBuilder._of(method, self)
|
||||
|
||||
func _get_mock_script(script: Script) -> Script:
|
||||
if _mock_script_cache.has(script):
|
||||
return _mock_script_cache.get(script)
|
||||
else:
|
||||
var mocked_script := _mock_generator.generate_mock_script(script)
|
||||
_mock_script_cache[script] = mocked_script
|
||||
return mocked_script
|
||||
|
||||
## Builder for specifying [VestMockDefs.Answer] objects
|
||||
class AnswerBuilder:
|
||||
var _test
|
||||
var _args: Array = []
|
||||
var _method: Callable
|
||||
|
||||
## Set expected arguments
|
||||
func with_args(p_args: Array) -> AnswerBuilder:
|
||||
_args = p_args
|
||||
return self
|
||||
|
||||
## Answer by calling a custom method
|
||||
## [br][br]
|
||||
## The method will received the passed arguments as an array.
|
||||
func then_answer(p_answer_method: Callable) -> void:
|
||||
var answer := VestMockDefs.Answer.new()
|
||||
answer.expected_method = _method
|
||||
answer.expected_args = _args
|
||||
answer._answer_method = p_answer_method
|
||||
|
||||
_test._mock_handler.add_answer(answer)
|
||||
|
||||
## Answer with a fixed value
|
||||
func then_return(p_answer_value: Variant) -> void:
|
||||
var answer := VestMockDefs.Answer.new()
|
||||
answer.expected_method = _method
|
||||
answer.expected_args = _args
|
||||
answer._answer_value = p_answer_value
|
||||
|
||||
_test._mock_handler.add_answer(answer)
|
||||
|
||||
static func _of(p_method: Callable, p_test):
|
||||
var builder := AnswerBuilder.new()
|
||||
builder._method = p_method
|
||||
builder._test = p_test
|
||||
return builder
|
||||
@@ -0,0 +1 @@
|
||||
uid://bj1basxqfa3
|
||||
@@ -0,0 +1,193 @@
|
||||
extends Object
|
||||
|
||||
var _define_stack: Array[VestDefs.Suite] = []
|
||||
var _result: VestResult.Case
|
||||
|
||||
var _expected_count := 0
|
||||
var _actual_count := 0
|
||||
|
||||
signal on_begin()
|
||||
signal on_suite_begin(suite: VestDefs.Suite)
|
||||
signal on_case_begin(case: VestDefs.Case)
|
||||
signal on_case_finish(case: VestDefs.Case)
|
||||
signal on_suite_finish(case: VestDefs.Case)
|
||||
signal on_finish()
|
||||
|
||||
func define(name: String, callback: Callable, is_only: bool = false) -> VestDefs.Suite:
|
||||
var suite = VestDefs.Suite.new()
|
||||
suite.name = name
|
||||
suite.is_only = is_only
|
||||
_define_stack.push_back(suite)
|
||||
|
||||
var userland_loc := _find_userland_stack_location()
|
||||
suite.definition_file = userland_loc[0]
|
||||
suite.definition_line = userland_loc[1]
|
||||
|
||||
await callback.call()
|
||||
|
||||
_define_stack.pop_back()
|
||||
if not _define_stack.is_empty():
|
||||
_define_stack.back().suites.push_back(suite)
|
||||
|
||||
return suite
|
||||
|
||||
func define_only(name: String, callback: Callable) -> VestDefs.Suite:
|
||||
return await define(name, callback, true)
|
||||
|
||||
func test(description: String, callback: Callable, is_only: bool = false, method_name: String = "") -> void:
|
||||
var case_def := VestDefs.Case.new()
|
||||
case_def.description = description
|
||||
case_def.is_only = is_only
|
||||
case_def.callback = callback
|
||||
case_def.method_name = method_name
|
||||
|
||||
var userland_loc := _find_userland_stack_location()
|
||||
case_def.definition_file = userland_loc[0]
|
||||
case_def.definition_line = userland_loc[1]
|
||||
|
||||
_define_stack.back().cases.push_back(case_def)
|
||||
|
||||
func test_only(description: String, callback: Callable, method_name: String = "") -> void:
|
||||
await test(description, callback, true, method_name)
|
||||
|
||||
func benchmark(name: String, callback: Callable) -> VestDefs.Benchmark:
|
||||
var result := VestDefs.Benchmark.new()
|
||||
result.name = name
|
||||
result.callback = callback
|
||||
result._test = self
|
||||
return result
|
||||
|
||||
func todo(message: String = "", data: Dictionary = {}) -> void:
|
||||
_with_result(VestResult.TEST_TODO, message, data)
|
||||
|
||||
func skip(message: String = "", data: Dictionary = {}) -> void:
|
||||
_with_result(VestResult.TEST_SKIP, message, data)
|
||||
|
||||
func fail(message: String = "", data: Dictionary = {}) -> void:
|
||||
_with_result(VestResult.TEST_FAIL, message, data)
|
||||
|
||||
func ok(message: String = "", data: Dictionary = {}) -> void:
|
||||
_with_result(VestResult.TEST_PASS, message, data)
|
||||
|
||||
func before_all():
|
||||
pass
|
||||
|
||||
func before_suite(_suite_def: VestDefs.Suite):
|
||||
pass
|
||||
|
||||
func before_case(_case_def: VestDefs.Case):
|
||||
pass
|
||||
|
||||
func after_case(_case_def: VestDefs.Case):
|
||||
pass
|
||||
|
||||
func after_suite(_suite_def: VestDefs.Suite):
|
||||
pass
|
||||
|
||||
func after_all():
|
||||
pass
|
||||
|
||||
func _init():
|
||||
pass
|
||||
|
||||
func _with_result(status: int, message: String, data: Dictionary):
|
||||
if message:
|
||||
_result.messages.append(message)
|
||||
|
||||
# Smartly gather "got" and "expected" data
|
||||
# - If there's just one assert that sends these, have the values as-is
|
||||
# - If multiple asserts send them, gather them into arrays for the user to check
|
||||
if data.has("got"):
|
||||
if _actual_count == 0:
|
||||
_result.data["got"] = data["got"]
|
||||
elif _actual_count == 1:
|
||||
_result.data["got"] = [_result.data["got"], data["got"]]
|
||||
elif _actual_count > 1:
|
||||
_result.data["got"].append(data["got"])
|
||||
data.erase("got")
|
||||
_actual_count += 1
|
||||
|
||||
if data.has("expect"):
|
||||
if _expected_count == 0:
|
||||
_result.data["expect"] = data["expect"]
|
||||
elif _expected_count == 1:
|
||||
_result.data["expect"] = [_result.data["expect"], data["expect"]]
|
||||
elif _expected_count > 1:
|
||||
_result.data["expect"].append(data["expect"])
|
||||
data.erase("expect")
|
||||
_expected_count += 1
|
||||
|
||||
_result.data.merge(data, true)
|
||||
|
||||
if _result.status != VestResult.TEST_VOID and status == VestResult.TEST_PASS:
|
||||
# Test already failed, don't override with PASS
|
||||
return
|
||||
|
||||
_result.status = status
|
||||
|
||||
var userland_loc := _find_userland_stack_location()
|
||||
_result.assert_file = userland_loc[0]
|
||||
_result.assert_line = userland_loc[1]
|
||||
|
||||
func _begin(what: Object):
|
||||
if what == self:
|
||||
await _emit_async(on_begin)
|
||||
await before_all()
|
||||
elif what is VestDefs.Suite:
|
||||
await _emit_async(on_suite_begin, [what])
|
||||
await before_suite(what)
|
||||
elif what is VestDefs.Case:
|
||||
_prepare_for_case(what)
|
||||
|
||||
await _emit_async(on_case_begin, [what])
|
||||
await before_case(what)
|
||||
else:
|
||||
assert(false, "Beginning unknown object: %s" % [what])
|
||||
|
||||
func _finish(what: Object):
|
||||
if what == self:
|
||||
await _emit_async(on_finish)
|
||||
await after_all()
|
||||
elif what is VestDefs.Suite:
|
||||
await _emit_async(on_suite_finish, [what])
|
||||
await after_suite(what)
|
||||
elif what is VestDefs.Case:
|
||||
_actual_count = 0
|
||||
_expected_count = 0
|
||||
|
||||
await _emit_async(on_case_finish, [what])
|
||||
await after_case(what)
|
||||
else:
|
||||
assert(false, "Finishing unknown object: %s" % [what])
|
||||
|
||||
func _prepare_for_case(case_def: VestDefs.Case):
|
||||
_result = VestResult.Case.new()
|
||||
_result.case = case_def
|
||||
|
||||
func _get_result() -> VestResult.Case:
|
||||
return _result
|
||||
|
||||
func _get_suite() -> VestDefs.Suite:
|
||||
return await define("OVERRIDE ME", func(): pass)
|
||||
|
||||
func _find_userland_stack_location() -> Array:
|
||||
var stack := get_stack()
|
||||
if stack.is_empty():
|
||||
return [(get_script() as Script).resource_path, -1]
|
||||
|
||||
var trimmed_stack := stack.filter(func(it): return not it["source"].begins_with("res://addons/vest"))
|
||||
if trimmed_stack.is_empty():
|
||||
return ["<unknown>", -1]
|
||||
else:
|
||||
return [trimmed_stack[0]["source"], trimmed_stack[0]["line"]]
|
||||
|
||||
func _emit_async(p_signal: Signal, p_args: Array = []) -> void:
|
||||
for connection in p_signal.get_connections():
|
||||
var callable := connection["callable"] as Callable
|
||||
var flags := connection["flags"] as ConnectFlags
|
||||
|
||||
if flags & CONNECT_DEFERRED:
|
||||
# TODO: Document call order and that deferred handlers are not awaited
|
||||
(func(): callable.callv(p_args)).call_deferred()
|
||||
else:
|
||||
await callable.callv(p_args)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cv6d02q04h0mk
|
||||
@@ -0,0 +1,4 @@
|
||||
extends "res://addons/vest/test/vest-test-base.gd"
|
||||
class_name VestTestMixin
|
||||
|
||||
## Base class for test mixins
|
||||
@@ -0,0 +1 @@
|
||||
uid://c8sve5b1njic0
|
||||
@@ -0,0 +1,9 @@
|
||||
# This file is generated by Vest!
|
||||
# Do not modify!
|
||||
# source: res://addons/vest/test/vest-test.gd
|
||||
extends "res://addons/vest/_generated-mixins/6-3c4a4dd7.gd"
|
||||
|
||||
class_name VestTest
|
||||
|
||||
func __get_vest_mixins() -> Array:
|
||||
return []
|
||||
@@ -0,0 +1 @@
|
||||
uid://rnjdivon87u7
|
||||
Reference in New Issue
Block a user