ajhahn.de
← Theria
GDScript 193 lines
extends Logger
class_name GutErrorTracker

# ------------------------------------------------------------------------------
# Static methods wrap around add/remove logger to make disabling the logger
# easier and to help avoid misusing add/remove in tests.  If GUT needs to
# add/remove a logger then this is how it should do it.
# ------------------------------------------------------------------------------
static var registered_loggers := {}
static var register_loggers = true

static func register_logger(which):
	if(register_loggers and !registered_loggers.has(which)):
		OS.add_logger(which)
		registered_loggers[which] = get_stack()


static func deregister_logger(which):
	if(registered_loggers.has(which)):
		OS.remove_logger(which)
		registered_loggers.erase(which)




# ------------------------------------------------------------------------------
# GutErrorTracker
# ------------------------------------------------------------------------------
var _current_test_id = GutUtils.NO_TEST
var _mutex = Mutex.new()

var errors = GutUtils.OneToMany.new()

var treat_gut_errors_as : GutUtils.TREAT_AS = GutUtils.TREAT_AS.FAILURE
var treat_engine_errors_as : GutUtils.TREAT_AS = GutUtils.TREAT_AS.FAILURE
var treat_push_error_as : GutUtils.TREAT_AS = GutUtils.TREAT_AS.FAILURE
var disabled = false


# ----------------
#region Private
# ----------------

func _get_stack_data(current_test_name):
	var test_entry = {}
	var stackTrace = get_stack()

	if(stackTrace!=null):
		var index = 0
		while(index < stackTrace.size() and test_entry == {}):
			var line = stackTrace[index]
			var function = line.get("function")
			if function == current_test_name:
				test_entry = stackTrace[index]
			else:
				index += 1

		for i in range(index):
			stackTrace.remove_at(0)

	return {
		"test_entry" = test_entry,
		"full_stack" = stackTrace
	}


func _is_error_failable(error : GutTrackedError):
	var is_it = false
	if(error.handled == false):
		if(error.is_gut_error()):
			is_it = treat_gut_errors_as == GutUtils.TREAT_AS.FAILURE
		elif(error.is_push_error()):
			is_it = treat_push_error_as == GutUtils.TREAT_AS.FAILURE
		elif(error.is_engine_error()):
			is_it = treat_engine_errors_as == GutUtils.TREAT_AS.FAILURE
	return is_it

# ----------------
#endregion
#region Godot's Logger Overrides
# ----------------

# Godot's Logger virtual method for errors
func _log_error(function: String, file: String, line: int,
	code: String, rationale: String, editor_notify: bool,
	error_type: int, script_backtraces: Array[ScriptBacktrace]) -> void:
		add_error(function, file, line,
			code, rationale, editor_notify,
			error_type, script_backtraces)

# Godot's Logger virtual method for any output?
# func _log_message(message: String, error: bool) -> void:
# 	pass

# ----------------
#endregion
#region Public
# ----------------

func start_test(test_id):
	_current_test_id = test_id


func end_test():
	_current_test_id = GutUtils.NO_TEST


func did_test_error(test_id=_current_test_id):
	return errors.size(test_id) > 0


func get_current_test_errors():
	return errors.items.get(_current_test_id, [])


# This should look through all the errors for a test and see if a failure
# should happen based off of flags.
func should_test_fail_from_errors(test_id = _current_test_id):
	var to_return = false
	if(errors.items.has(test_id)):
		var errs = errors.items[test_id]
		var index = 0
		while(index < errs.size() and !to_return):
			var error = errs[index]
			to_return = _is_error_failable(error)
			index += 1
	return to_return


func get_errors_for_test(test_id=_current_test_id):
	var to_return = []
	if(errors.items.has(test_id)):
		to_return = errors.items[test_id].duplicate()

	return to_return


# Returns emtpy string or text for errors that occurred during the test that
# should cause failure based on this class' flags.
func get_fail_text_for_errors(test_id=_current_test_id) -> String:
	var error_texts = []

	if(errors.items.has(test_id)):
		for error in errors.items[test_id]:
			if(_is_error_failable(error)):
				error_texts.append(str('<', error.get_error_type_name(), '>', error.code))

	var to_return = ""
	for i in error_texts.size():
		if(to_return != ""):
			to_return += "\n"
		to_return += str("[", i + 1, "] ", error_texts[i])

	return to_return


func add_gut_error(text) -> GutTrackedError:
	if(_current_test_id != GutUtils.NO_TEST):
		var data = _get_stack_data(_current_test_id)
		if(data.test_entry != {}):
			return add_error(_current_test_id, data.test_entry.source, data.test_entry.line,
				text, '', false,
				GutUtils.GUT_ERROR_TYPE, data.full_stack)

	return add_error(_current_test_id, "unknown", -1,
		text, '', false,
		GutUtils.GUT_ERROR_TYPE, get_stack())


func add_error(function: String, file: String, line: int,
	code: String, rationale: String, editor_notify: bool,
	error_type: int, script_backtraces: Array) -> GutTrackedError:
		if(disabled):
			return

		_mutex.lock()

		var err := GutTrackedError.new()
		err.backtrace = script_backtraces
		err.code = code
		err.rationale = rationale
		err.error_type = error_type
		err.editor_notify = editor_notify
		err.file = file
		err.function = function
		err.line = line

		errors.add(_current_test_id, err)

		_mutex.unlock()

		return err