extends RefCounted class_name TestHelper ## Common test utilities and assertions for Skelly project testing ## ## Provides standardized testing functions, assertions, and utilities ## to ensure consistent test behavior across all test files. ## Test result tracking static var tests_run := 0 static var tests_passed := 0 static var tests_failed := 0 ## Performance tracking static var test_start_time := 0.0 static var performance_data := {} ## Print test section header with consistent formatting static func print_test_header(test_name: String): print("\n=== Testing %s ===" % test_name) tests_run = 0 tests_passed = 0 tests_failed = 0 test_start_time = Time.get_unix_time_from_system() ## Print test section footer with results summary static func print_test_footer(test_name: String): var end_time = Time.get_unix_time_from_system() var duration = end_time - test_start_time print("\n--- %s Results ---" % test_name) print("Tests Run: %d" % tests_run) print("Passed: %d" % tests_passed) print("Failed: %d" % tests_failed) print("Duration: %.3f seconds" % duration) if tests_failed == 0: print("✅ All tests PASSED") else: print("❌ %d tests FAILED" % tests_failed) print("=== %s Complete ===" % test_name) ## Assert that a condition is true static func assert_true(condition: bool, message: String = ""): tests_run += 1 if condition: tests_passed += 1 print("✅ PASS: %s" % message) else: tests_failed += 1 print("❌ FAIL: %s" % message) ## Assert that a condition is false static func assert_false(condition: bool, message: String = ""): assert_true(not condition, message) ## Assert that two values are equal static func assert_equal(expected, actual, message: String = ""): var condition = expected == actual var full_message = message if not full_message.is_empty(): full_message += " " full_message += "(Expected: %s, Got: %s)" % [str(expected), str(actual)] assert_true(condition, full_message) ## Assert that two values are not equal static func assert_not_equal(expected, actual, message: String = ""): var condition = expected != actual var full_message = message if not full_message.is_empty(): full_message += " " full_message += "(Should not equal: %s, Got: %s)" % [str(expected), str(actual)] assert_true(condition, full_message) ## Assert that a value is null static func assert_null(value, message: String = ""): assert_true(value == null, message + " (Should be null, got: %s)" % str(value)) ## Assert that a value is not null static func assert_not_null(value, message: String = ""): assert_true(value != null, message + " (Should not be null)") ## Assert that a value is within a range static func assert_in_range(value: float, min_val: float, max_val: float, message: String = ""): var condition = value >= min_val and value <= max_val var full_message = "%s (Value: %f, Range: %f-%f)" % [message, value, min_val, max_val] assert_true(condition, full_message) ## Assert that two floating-point values are approximately equal (with tolerance) static func assert_float_equal(expected: float, actual: float, tolerance: float = 0.0001, message: String = ""): # Handle special cases: both infinity, both negative infinity, both NaN if is_inf(expected) and is_inf(actual): var condition = (expected > 0) == (actual > 0) # Same sign of infinity var full_message = "%s (Both infinity values: Expected: %f, Got: %f)" % [message, expected, actual] assert_true(condition, full_message) return if is_nan(expected) and is_nan(actual): var full_message = "%s (Both NaN values: Expected: %f, Got: %f)" % [message, expected, actual] assert_true(true, full_message) # Both NaN is considered equal return # Normal floating-point comparison var difference = abs(expected - actual) var condition = difference <= tolerance var full_message = "%s (Expected: %f, Got: %f, Difference: %f, Tolerance: %f)" % [message, expected, actual, difference, tolerance] assert_true(condition, full_message) ## Assert that an array contains a specific value static func assert_contains(array: Array, value, message: String = ""): var condition = value in array var full_message = "%s (Array: %s, Looking for: %s)" % [message, str(array), str(value)] assert_true(condition, full_message) ## Assert that an array does not contain a specific value static func assert_not_contains(array: Array, value, message: String = ""): var condition = not (value in array) var full_message = "%s (Array: %s, Should not contain: %s)" % [message, str(array), str(value)] assert_true(condition, full_message) ## Assert that a dictionary has a specific key static func assert_has_key(dict: Dictionary, key, message: String = ""): var condition = dict.has(key) var full_message = "%s (Dictionary keys: %s, Looking for: %s)" % [message, str(dict.keys()), str(key)] assert_true(condition, full_message) ## Assert that a file exists static func assert_file_exists(path: String, message: String = ""): var condition = FileAccess.file_exists(path) var full_message = "%s (Path: %s)" % [message, path] assert_true(condition, full_message) ## Assert that a file does not exist static func assert_file_not_exists(path: String, message: String = ""): var condition = not FileAccess.file_exists(path) var full_message = "%s (Path: %s)" % [message, path] assert_true(condition, full_message) ## Performance testing - start timing static func start_performance_test(test_id: String): performance_data[test_id] = Time.get_unix_time_from_system() ## Performance testing - end timing and validate static func end_performance_test(test_id: String, max_duration_ms: float, message: String = ""): if not performance_data.has(test_id): assert_true(false, "Performance test '%s' was not started" % test_id) return var start_time = performance_data[test_id] var end_time = Time.get_unix_time_from_system() var duration_ms = (end_time - start_time) * 1000.0 var condition = duration_ms <= max_duration_ms var full_message = "%s (Duration: %.2fms, Max: %.2fms)" % [message, duration_ms, max_duration_ms] assert_true(condition, full_message) performance_data.erase(test_id) ## Create a temporary test file with content static func create_temp_file(filename: String, content: String = "") -> String: var temp_path = "user://test_" + filename var file = FileAccess.open(temp_path, FileAccess.WRITE) if file: file.store_string(content) file.close() return temp_path ## Clean up temporary test file static func cleanup_temp_file(path: String): if FileAccess.file_exists(path): DirAccess.remove_absolute(path) ## Create invalid JSON content for testing static func create_invalid_json() -> String: return '{"invalid": json, missing_quotes: true, trailing_comma: true,}' ## Create valid test JSON content static func create_valid_json() -> String: return '{"test_key": "test_value", "test_number": 42, "test_bool": true}' ## Wait for a specific number of frames static func wait_frames(frames: int, node: Node): for i in range(frames): await node.get_tree().process_frame ## Mock a simple function call counter class MockCallCounter: var call_count := 0 var last_args := [] func call_function(args: Array = []): call_count += 1 last_args = args.duplicate() func reset(): call_count = 0 last_args.clear() ## Create a mock call counter for testing static func create_mock_counter() -> MockCallCounter: return MockCallCounter.new() ## Validate that an object has expected properties static func assert_has_properties(object: Object, properties: Array, message: String = ""): for property in properties: var condition = property in object var full_message = "%s - Missing property: %s" % [message, property] assert_true(condition, full_message) ## Validate that an object has expected methods static func assert_has_methods(object: Object, methods: Array, message: String = ""): for method in methods: var condition = object.has_method(method) var full_message = "%s - Missing method: %s" % [message, method] assert_true(condition, full_message) ## Print a test step with consistent formatting static func print_step(step_name: String): print("\n--- Test: %s ---" % step_name)