From f08dba648317870b4e11fb18a14c473e3574aa7e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 11 Mar 2025 18:40:00 +1100 Subject: [PATCH] Improve debug logs when running BDD tests I've been spoilt by tools like Behat and so this make it much nicer because it tells you exactly the feature/scenario/step where it failed. --- src/bonsai/test/bim/conftest.py | 19 +++++++++++++++++++ src/bonsai/test/bim/test_feature.py | 21 ++++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 src/bonsai/test/bim/conftest.py diff --git a/src/bonsai/test/bim/conftest.py b/src/bonsai/test/bim/conftest.py new file mode 100644 index 0000000000..2d69fe415a --- /dev/null +++ b/src/bonsai/test/bim/conftest.py @@ -0,0 +1,19 @@ +import pytest + +# pytest by default doesn't print steps and where it failed. Let's fix that. + + +@pytest.hookimpl +def pytest_bdd_before_scenario(request, feature, scenario): + print(f"\033[94m# {feature.name}\033[0m") + print(f"\033[94m## {scenario.name}\033[0m") + + +@pytest.hookimpl(tryfirst=True) +def pytest_bdd_after_step(request, feature, scenario, step, step_func, step_func_args): + print(f"\033[92m>>> {step.name}\033[0m") + + +@pytest.hookimpl(tryfirst=True) +def pytest_bdd_step_error(request, feature, scenario, step, step_func, step_func_args): + print(f"\033[1;91m>>> {step.name} <-- FAILED\033[0m") diff --git a/src/bonsai/test/bim/test_feature.py b/src/bonsai/test/bim/test_feature.py index 94ab5db19f..e6932cb1b5 100644 --- a/src/bonsai/test/bim/test_feature.py +++ b/src/bonsai/test/bim/test_feature.py @@ -18,6 +18,7 @@ import os import bpy +import pytest import traceback import webbrowser import numpy as np @@ -542,6 +543,7 @@ def i_press_operator(operator): @given(parsers.parse('I click "{button}"')) @when(parsers.parse('I click "{button}"')) +@then(parsers.parse('I click "{button}"')) def i_click_button(button): panel_spy.refresh_spy() for spied_operator in panel_spy.spied_operators: @@ -559,6 +561,8 @@ def i_click_button(button): # Clicked confirm on an operator's draw dialog return i_press_operator(panel_spy.panel.bl_idname) debug = "\n".join([f"{i} {v}" for i, v in enumerate(panel_spy.spied_operators)]) + if not debug: + debug = f"No buttons were found, here is the text we see: {panel_spy.spied_labels}" assert False, f"Could not find {button}:\n{debug}" @@ -603,6 +607,7 @@ def i_refresh_the_selected_objects(): bonsai.bim.handler.active_object_callback() +@given("I deselect all objects") @when("I deselect all objects") def i_deselect_all_objects(): bpy.context.view_layer.objects.active = None @@ -668,7 +673,9 @@ def then_the_object_name_is_placed_in_the_collection_collection(name: str, colle def additionally_the_object_name_is_selected(name): obj = bpy.context.scene.objects.get(name) if not obj: - assert False, f'The object "{name}" could not be selected' + total = len(bpy.context.scene.objects) + debug = "\n".join([o.name for o in bpy.context.scene.objects]) + assert False, f'The object "{name}" could not be selected. Available objects ({total} total):\n{debug}' bpy.context.view_layer.objects.active = obj obj.select_set(True) @@ -729,6 +736,8 @@ def nothing_happens(): pass +@given(parsers.parse('the object "{name}" exists')) +@when(parsers.parse('the object "{name}" exists')) @then(parsers.parse('the object "{name}" exists')) def the_object_name_exists(name: str) -> bpy.types.Object: # Some objects from linked collections may share the same name. This disambiguates them. @@ -1335,10 +1344,14 @@ def construction_type(relating_type_name): @when("I toggle edit mode") @then("I toggle edit mode") def i_toggle_edit_mode(): + props = tool.Geometry.get_geometry_props() + print(f"Toggling from {bpy.context.mode} / {props.mode} ...") + print("Selected items:", bpy.context.active_object, bpy.context.selected_objects) if bpy.context.mode == "OBJECT": bpy.ops.bim.override_mode_set_edit() else: bpy.ops.bim.override_mode_set_object() + print(f"... mode is now {bpy.context.mode} / {props.mode}") @when("I move the cursor to the bottom left corner") @@ -1357,8 +1370,10 @@ def prepare_undo(): @when(parsers.parse("I undo")) @then(parsers.parse("I undo")) def hit_undo(): - bpy.ops.ed.undo_push(message="UNDO STEP") - bpy.ops.ed.undo() + # bpy.ops.ed.undo_push(message="UNDO STEP") + override = tool.Blender.get_viewport_context() + with bpy.context.temp_override(**override): + bpy.ops.ed.undo() @then(parsers.parse('the object "{obj_name1}" has a connection with "{obj_name2}"'))