Merge branch 'blender-v4.5-release'

This commit is contained in:
Sean Kim
2025-06-12 11:28:37 -07:00
4 changed files with 277 additions and 66 deletions

View File

@@ -73,8 +73,8 @@ function(add_blender_test_io testname)
endfunction()
if(WITH_UI_TESTS)
set(_blender_headless_env_vars "BLENDER_BIN=${TEST_BLENDER_EXE}")
if(WITH_UI_TESTS_HEADLESS)
set(_blender_headless_env_vars "BLENDER_BIN=${TEST_BLENDER_EXE}")
# Currently only WAYLAND is supported, support for others may be added later.
# In this case none of the WESTON environment variables will be used.
@@ -104,43 +104,29 @@ if(WITH_UI_TESTS)
)
endif()
endif()
function(add_blender_test_ui testname)
# Remove `--background` so headless execution uses a GUI
# (within a headless graphical environment).
set(EXE_PARAMS ${TEST_BLENDER_EXE_PARAMS})
list(REMOVE_ITEM EXE_PARAMS --background)
add_blender_test_impl(
"${testname}"
"${_blender_headless_env_vars}"
"${TEST_PYTHON_EXE}"
"${CMAKE_SOURCE_DIR}/tests/utils/blender_headless.py"
# NOTE: attempting to maximize the window causes problems with a headless `weston`,
# while this could be investigated, use windowed mode instead.
# Use a window size that balances software GPU rendering with enough room to use the UI.
--factory-startup
-p 0 0 800 600
"${EXE_PARAMS}"
"${ARGN}"
)
endfunction()
else()
function(add_blender_test_ui testname)
# Remove `--background`
set(EXE_PARAMS ${TEST_BLENDER_EXE_PARAMS})
list(REMOVE_ITEM EXE_PARAMS --background)
add_blender_test_impl(
"${testname}"
""
"${TEST_BLENDER_EXE}"
--factory-startup
-p 0 0 800 600
${EXE_PARAMS}
${ARGN}
)
endfunction()
list(APPEND _blender_headless_env_vars
"PASS_THROUGH=1"
)
endif()
function(add_blender_test_ui testname)
# Remove `--background`a
set(EXE_PARAMS ${TEST_BLENDER_EXE_PARAMS})
list(REMOVE_ITEM EXE_PARAMS --background)
add_blender_test_impl(
"${testname}"
"${_blender_headless_env_vars}"
"${TEST_PYTHON_EXE}"
"${CMAKE_SOURCE_DIR}/tests/utils/blender_headless.py"
# NOTE: attempting to maximize the window causes problems with a headless `weston`,
# while this could be investigated, use windowed mode instead.
# Use a window size that balances software GPU rendering with enough room to use the UI.
--factory-startup
-p 0 0 800 600
"${EXE_PARAMS}"
"${ARGN}"
)
endfunction()
endif()
# Run Python script outside Blender.
@@ -1298,7 +1284,12 @@ if(WITH_UI_TESTS)
# This could be generated with:
# `"${TEST_PYTHON_EXE}" "${CMAKE_CURRENT_LIST_DIR}/ui_simulate/run.py" --list-tests`
# list explicitly so changes bisecting/updated are sure to re-run CMake.
set(_undo_tests
set(_ui_tests
test_workspace.sanity_check_general
test_workspace.sanity_check_2d_animation
test_workspace.sanity_check_sculpting
test_workspace.sanity_check_vfx
test_workspace.sanity_check_video_editing
test_undo.text_editor_edit_mode_mix
test_undo.text_editor_simple
test_undo.view3d_edit_mode_multi_window
@@ -1316,7 +1307,7 @@ if(WITH_UI_TESTS)
test_undo.view3d_texture_paint_complex
test_undo.view3d_texture_paint_simple
)
foreach(ui_test ${_undo_tests})
foreach(ui_test ${_ui_tests})
add_blender_test_ui(
"ui_${ui_test}"
--enable-event-simulate
@@ -1325,7 +1316,7 @@ if(WITH_UI_TESTS)
--tests "${ui_test}"
)
endforeach()
unset(_undo_tests)
unset(_ui_tests)
endif()

View File

@@ -23,6 +23,7 @@ For an editor to follow the tests:
import os
import sys
import tempfile
def create_parser():
@@ -137,7 +138,7 @@ def _process_test_id_fn(env, args, test_id):
return test_id, callproc.returncode == 0
def main():
def run(empty_user_dir):
directory = os.path.dirname(__file__)
if "--list-tests" in sys.argv:
list_tests(directory)
@@ -164,6 +165,7 @@ def main():
env = os.environ.copy()
env.update({
"LSAN_OPTIONS": "exitcode=0",
"BLENDER_USER_RESOURCES": empty_user_dir,
})
# We could support multiple tests per Blender session.
@@ -188,6 +190,13 @@ def main():
for test_id, ok in results:
print("OK: " if ok else "FAIL:", test_id)
return 0
def main():
with tempfile.TemporaryDirectory() as empty_user_dir:
sys.exit(run(empty_user_dir))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,174 @@
# SPDX-FileCopyrightText: 2025 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
"""
This file does not run anything, it's methods are accessed for tests by: ``run.py``.
"""
def _test_window(windows_exclude=None):
import bpy
wm = bpy.data.window_managers[0]
if windows_exclude is None:
return wm.windows[0]
for window in wm.windows:
if window not in windows_exclude:
return window
return None
def _test_vars(window):
import unittest
from modules.easy_keys import EventGenerate
return (
EventGenerate(window),
unittest.TestCase(),
)
def _call_by_name(e, text: str):
yield e.f3()
yield e.text(text)
yield e.ret()
def _call_menu(e, text: str):
yield e.f3()
yield e.text_unicode(text.replace(" -> ", " \u25b8 "))
yield e.ret()
def sanity_check_general():
e, t = _test_vars(window := _test_window())
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.a() # Animation
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Animation")
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.c() # Compositing
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Compositing")
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.g() # Geometry Nodes
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Geometry Nodes")
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.l() # Layout
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Layout")
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.m() # Modeling
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Modeling")
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.r() # Rendering
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Rendering")
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.s() # Scripting
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Scripting")
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.p() # Sculpting
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Sculpting")
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.h() # Shading
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Shading")
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.t() # Texture Paint
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Texture Paint")
yield from _call_by_name(e, "Add Workspace")
yield e.g() # General
yield e.u() # UV Editing
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "UV Editing")
def sanity_check_2d_animation():
e, t = _test_vars(window := _test_window())
yield from _call_by_name(e, "Add Workspace")
yield e.d() # 2D Animation
yield e.d() # 2D Animation
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "2D Animation")
yield from _call_by_name(e, "Add Workspace")
yield e.d() # 2D Animation
yield e.f() # 2D Full Canvas
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "2D Full Canvas")
yield from _call_by_name(e, "Add Workspace")
yield e.d() # 2D Animation
yield e.c() # Compositing
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Compositing")
yield from _call_by_name(e, "Add Workspace")
yield e.d() # 2D Animation
yield e.r() # Rendering
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Rendering")
def sanity_check_sculpting():
e, t = _test_vars(window := _test_window())
yield from _call_by_name(e, "Add Workspace")
yield e.s() # Sculpting
yield e.s() # Sculpting
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Sculpting")
yield from _call_by_name(e, "Add Workspace")
yield e.s() # Sculpting
yield e.h() # Shading
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Shading")
def sanity_check_vfx():
e, t = _test_vars(window := _test_window())
yield from _call_by_name(e, "Add Workspace")
yield e.v() # VFX
yield e.c() # Compositing
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Compositing")
yield from _call_by_name(e, "Add Workspace")
yield e.v() # VFX
yield e.m() # Masking
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Masking")
yield from _call_by_name(e, "Add Workspace")
yield e.v() # VFX
yield e.t() # Motion Tracking
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Motion Tracking")
yield from _call_by_name(e, "Add Workspace")
yield e.v() # VFX
yield e.r() # Rendering
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Rendering")
def sanity_check_video_editing():
e, t = _test_vars(window := _test_window())
yield from _call_by_name(e, "Add Workspace")
yield e.e() # Video Editing
yield e.r() # Rendering
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Rendering")
yield from _call_by_name(e, "Add Workspace")
yield e.e() # Video Editing
yield e.v() # Video Editing
t.assertEqual(window.workspace.name_full.split(".", 1)[0], "Video Editing")