bitwes / Gut

Godot Unit Test. Unit testing tool for Godot Game Engine.
1.71k stars 97 forks source link

assert_called ignores calls to doubled AudioStreamPlayer3D's methods #633

Open encrest opened 6 days ago

encrest commented 6 days ago

Versions

(list all versions where you have replicated the bug)

The Bug

assert_called and assert_called_times appear to be ignoring calls to a doubled AudioStreamPlayer3D's methods, and will fail in the below minimum reproducible example with the following output. I also tried without the DOUBLE_STRATEGY.INCLUDE_NATIVE param and saw the same result, and I see the same effect with other methods like "set_volume_db" and "set_pitch_scale".

I've gotten other doubles to work as I would expect such as a partial_double(Node3D) and a double of one of my own custom gdscript classes, so I'm not sure what I could've done wrong/differently here with AudioStreamPlayer3D.

Thanks in advance for any pointers!

===================================

res://scripts/tests/unit/test_gut.gd

---- Totals ---- Scripts 1 Tests 1 Passing none Failing 1 Asserts 1 Time 2.127s

[Orphans]: 1 new orphan in total. Note: This count does not include GUT objects that will be freed upon exit. It also does not include any orphans created by global scripts loaded before tests were ran. Total orphans = 2

Ran Scripts matching "test_gut.gd" Ran Tests matching "test_double_updates_count" ---- 1 failing tests ----

Steps To Reproduce

  1. Add the following code to a test file
  2. Open the file in vscode, put your cursor on the test function title
  3. Use the command GUT: Run at Cursor

Minimum reproducible example code:

extends GutTest

func test_double_updates_count():
    var audio_player: AudioStreamPlayer3D = double(AudioStreamPlayer3D, DOUBLE_STRATEGY.INCLUDE_NATIVE).new()
    stub(audio_player, "play").to_do_nothing()
    audio_player.play()
    assert_called(audio_player, "play")
bitwes commented 5 days ago

TL;DR. If you remove the static typing for audio_player the test will pass. It has to do with native method overrides and the engine not calling overrides generated by the double. This issue has some info: https://github.com/godotengine/godot/issues/55024

I think this is a limitation with Godot and is an example of why they introduced the "Native Method Override" warning. In the following test, assert_called fails, but the assert_eq passes. This means set_position was called and it worked but it shouldn't have because dbl_node2d is not a partial_double and set_position was not stubbed. So I'm guessing the Engine called the "native method" set_position directly without using the method in the double. It seems that when the variable is not statically typed the engine cannot make this optimization and ends up calling the double's method.

func test_double_node2d():
    var dbl_node2d : Node2D = double(Node2D).new()
    dbl_node2d.set_position(Vector2(100, 100))
    assert_called(dbl_node2d, 'set_position')
    assert_eq(dbl_node2d.position, Vector2(100, 100))

In another example, I created a Node2D subclass and the methods in the class are tracked as being called, but set_position is not. If you remove the static typing then set_position is tracked but the assert_eq fails because set_position was not stubbed.

class ThisThing:
    extends Node2D

    func say_something():
        print('something')

func test_double_this_guy():
    register_inner_classes(get_script())
    var dbl_this_thing : ThisThing = partial_double(ThisThing, DOUBLE_STRATEGY.INCLUDE_NATIVE).new()
    dbl_this_thing.set_position(Vector2(100, 100))
    dbl_this_thing.say_something()
    assert_called(dbl_this_thing, 'set_position')
    assert_called(dbl_this_thing, 'say_something')
    assert_eq(dbl_this_thing.position, Vector2(100, 100))

This should be documented in GUT. I don't think it ever occurred to me to statically type double vars. If you are doing it to avoid warnings, the next release of GUT (currently in main) has some changes that will turn off warnings when loading and running tests so at least your output is less noisy...though you may still have some squiggly lines in the editor.

encrest commented 5 days ago

Thanks so much for the quick response! Removing the static type declaration on the double fixed the test for me! For posterity, I swapped in this line for the audio_player declaration: var audio_player = double(AudioStreamPlayer3D, DOUBLE_STRATEGY.INCLUDE_NATIVE).new()

In my actual game test, I also had to remove the static type declaration on the class property I was assigning the double to as well.

bitwes commented 5 days ago

Reopening until documentation has been added and because having to change the thing using it is ugly.

I'm not sure if there will be any other way to handle that, but I'd like to explore it a bit. It should at least be included in the documentation.