bitwes/Gut

Wait for a coroutine to complete or a timeout

doctor-g opened this issue · 2 comments

Versions

This is specific to Godot 4 and is related to #382.

The Feature

I would like to have a test wait for a coroutine to complete or a particular amount of time to pass. This is similar to the existing support for wait_for_signal.

Here's an example. Consider an adventure object that has a run coroutine. In the game, it is called as await adventure.run(). In the test, I would like to call that same method, but have the test fail if some time elapses.

Unfortunately, this won't work

wait_for_signal(await adventure.run(), WAIT_TIME)

because it will have to wait for the coroutine to finish before even calling wait_for_signal, and this won't work

wait_for_signal(adventure.run(), WAIT_TIME)

because the interpreter recognizes that adventure.run() is a coroutine and must be called with await.

A syntax like this would be convenient:

wait_for_coroutine(adventure.run, 3.0)

I don't think it is possible to stop waiting on the co-routine if the maximum amount of time is exceeded. Something, eventually, has to do an await on the method. At that point you must wait until the method has finished executing, and you can't stop waiting on it.

You might be able to do something tricky with threads, but things get messy when the method does not finish in time.
Trying to free an object that "busy" will cause errors, so you can't clean up things created in the test until the method finishes executing. This means you might as well wait until the method finishes anyway.

You could roll your own assert_execution_time. You can add it to the script where you need it or make your own base script that all your tests inherit from (instead of inheriting GutTest directly). It can't stop early, but it does make running the method and asserting the time taken a lot easier.

extends GutTest

func assert_execute_time(callable : Callable, seconds):
	var start_time = Time.get_ticks_msec()
	await callable.call()
	var total = float(Time.get_ticks_msec() - start_time) * .001
	var msg = str('Expected [', callable, '] to finish executing in ', seconds, 's')
	assert_lt(total, seconds, msg)

func test_example_using_assert_execute_time():
	await assert_execute_time(wait_seconds.bind(1.0), 2.0)

I don't think I would want to add this to GUT though. It might be nice, but wouldn't be complete without numerous other flavors like assert_execute_time_gt, assert_execute_time_between, assert_execute_time_lt.

Let me know what you think. If there's is more interest in adding these kinds of asserts I'd reconsider it, but it feels a little too specific.

I was hoping for a way to make the test fail instead of hang in cases where the coroutine misbehaves. I did a little investigation into this, too, expecting that coroutines would have some kind of implicit signal, but it seems they don't. It looks like this is simply not possible in GDScript, unfortunately.

Thanks for looking at it, anyway. Seems like it's a restriction of the language, not of your system.