blender/tests/python/bl_blendfile_versioning.py
Bastien Montagne bc80ef136e Big Endian Support Removal.
This commit implements #125759.

It removes:
* Blender does not build on big endian systems anymore.
* Support for opening blendfiles written from a big endian system is
  removed.

It keeps:
* Support to generate thumbnails from big endian blendfiles.
* BE support in `extern` or `intern` libraries, including Cycles.
* Support to open big endian versions of third party file formats:
  - PLY files.
  - Some image files (cineon, ...).

Pull Request: https://projects.blender.org/blender/blender/pulls/140138
2025-06-12 10:37:47 +02:00

395 lines
17 KiB
Python

# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: Apache-2.0
# ./blender.bin --background --python tests/python/bl_blendfile_versioning.py ..
# WARNING(@ideasman42): some blend files causes the tests to fail (seemingly) at random (on Linux & macOS at least).
# Take care when adding new files as they may break on other platforms, frequently but not on every execution.
#
# This needs to be investigated!
__all__ = (
"main",
)
import os
import platform
import sys
import bpy
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from bl_blendfile_utils import TestHelper
class TestBlendFileOpenAllTestFiles(TestHelper):
def __init__(self, args):
self.args = args
# Some files are known broken currently.
# Each file in this list should either be the source of a bug report,
# or removed from tests repo.
self.excluded_paths = {
# modifier_stack/explode_modifier.blend
# BLI_assert failed: source/blender/blenlib/BLI_ordered_edge.hh:41, operator==(), at 'e1.v_low < e1.v_high'
"explode_modifier.blend",
# depsgraph/deg_anim_camera_dof_driving_material.blend
# ERROR (bke.fcurve):
# source/blender/blenkernel/intern/fcurve_driver.cc:188 dtar_get_prop_val:
# Driver Evaluation Error: cannot resolve target for OBCamera ->
# data.dof_distance
"deg_anim_camera_dof_driving_material.blend",
# depsgraph/deg_driver_shapekey_same_datablock.blend
# Error: Not freed memory blocks: 4, total unfreed memory 0.000427 MB
"deg_driver_shapekey_same_datablock.blend",
# physics/fluidsim.blend
# Error: Not freed memory blocks: 3, total unfreed memory 0.003548 MB
"fluidsim.blend",
# opengl/ram_glsl.blend
# Error: Not freed memory blocks: 4, total unfreed memory 0.000427 MB
"ram_glsl.blend",
}
# Some files are expected to be invalid.
# This mapping stores filenames as keys, and expected error message as value.
self.invalid_paths = {
# animation/driver-object-eyes.blend
# File generated from a big endian build of Blender.
"driver-object-eyes.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# modeling/faceselectmode.blend
# File generated from a big endian build of Blender.
"faceselectmode.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# modeling/weight-paint_test.blend
# File generated from a big endian build of Blender.
"weight-paint_test.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/1.62/glass.blend
# File generated from a big endian build of Blender.
"glass.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/1.62/room.blend
# File generated from a big endian build of Blender.
"room.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/1.69/s-mesh.blend
# File generated from a big endian build of Blender.
"s-mesh.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/1.70/escher.blend
# File generated from a big endian build of Blender.
"escher.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/1.98/egypt.blend
# File generated from a big endian build of Blender.
"egypt.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.25/FroggyPacked.blend
# File generated from a big endian build of Blender.
"FroggyPacked.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.30/CtrlObject.blend
# File generated from a big endian build of Blender.
"CtrlObject.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.30/demofile.blend
# File generated from a big endian build of Blender.
"demofile.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.30/dolphin.blend
# File generated from a big endian build of Blender.
"dolphin.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.30/mball.blend
# File generated from a big endian build of Blender.
"mball.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.30/motor9.blend
# File generated from a big endian build of Blender.
"motor9.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.30/relative.blend
# File generated from a big endian build of Blender.
"relative.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.31/Raptor.blend
# File generated from a big endian build of Blender.
"Raptor.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.31/allselect.blend
# File generated from a big endian build of Blender.
"allselect.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.31/arealight.blend
# File generated from a big endian build of Blender.
"arealight.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.31/hairball.blend
# File generated from a big endian build of Blender.
"hairball.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.31/luxo.blend
# File generated from a big endian build of Blender.
"luxo.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.31/monkey_cornelius.blend
# File generated from a big endian build of Blender.
"monkey_cornelius.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.31/refract_monkey.blend
# File generated from a big endian build of Blender.
"refract_monkey.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.31/robo.blend
# File generated from a big endian build of Blender.
"robo.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.34/flippedmatrixes.blend
# File generated from a big endian build of Blender.
"flippedmatrixes.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.34/lostride.blend
# File generated from a big endian build of Blender.
"lostride.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.34/tapercurve.blend
# File generated from a big endian build of Blender.
"tapercurve.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.36/pathdist.blend
# File generated from a big endian build of Blender.
"pathdist.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
# io_tests/blend_big_endian/2.76/bird_sintel.blend
# File generated from a big endian build of Blender.
"bird_sintel.blend": (
(OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0"
),
}
# Directories to exclude relative to `./tests/files/`.
self.excluded_dirs = ()
assert all(p.endswith("/") for p in self.excluded_dirs)
self.excluded_dirs = tuple(p.replace("/", os.sep) for p in self.excluded_dirs)
# Generate the slice of blendfile paths that this instance of the test should process.
blendfile_paths = [p for p in self.iter_blendfiles_from_directory(self.args.src_test_dir)]
# `os.scandir()` used by `iter_blendfiles_from_directory` does not
# guarantee any form of order.
blendfile_paths.sort()
slice_indices = self.generate_slice_indices(len(blendfile_paths), self.args.slice_range, self.args.slice_index)
self.blendfile_paths = blendfile_paths[slice_indices[0]:slice_indices[1]]
@classmethod
def iter_blendfiles_from_directory(cls, root_path):
for dir_entry in os.scandir(root_path):
if dir_entry.is_dir(follow_symlinks=False):
yield from cls.iter_blendfiles_from_directory(dir_entry.path)
elif dir_entry.is_file(follow_symlinks=False):
if os.path.splitext(dir_entry.path)[1] == ".blend":
yield dir_entry.path
@staticmethod
def generate_slice_indices(total_len, slice_range, slice_index):
slice_stride_base = total_len // slice_range
slice_stride_remain = total_len % slice_range
def gen_indices(i):
return (
(i * (slice_stride_base + 1))
if i < slice_stride_remain else
(slice_stride_remain * (slice_stride_base + 1)) + ((i - slice_stride_remain) * slice_stride_base)
)
slice_indices = [(gen_indices(i), gen_indices(i + 1)) for i in range(slice_range)]
return slice_indices[slice_index]
def skip_path_check(self, bfp):
if os.path.basename(bfp) in self.excluded_paths:
return True
if self.excluded_dirs:
assert bfp.startswith(self.args.src_test_dir)
bfp_relative = bfp[len(self.args.src_test_dir):].rstrip(os.sep)
if bfp_relative.startswith(*self.excluded_dirs):
return True
return False
def invalid_path_exception_process(self, bfp, exception):
expected_failure = self.invalid_paths.get(os.path.basename(bfp), None)
if not expected_failure:
raise exception
# Check expected exception type(s).
if not isinstance(exception, expected_failure[0]):
raise exception
# Check expected exception (partial) message.
if expected_failure[1] not in str(exception):
raise exception
print(f"\tExpected failure: '{exception}'", flush=True)
def test_open(self):
for bfp in self.blendfile_paths:
if self.skip_path_check(bfp):
continue
if not self.args.is_quiet:
print(f"Trying to open {bfp}", flush=True)
bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
try:
bpy.ops.wm.open_mainfile(filepath=bfp, load_ui=False)
except BaseException as e:
self.invalid_path_exception_process(bfp, e)
def link_append(self, do_link):
operation_name = "link" if do_link else "append"
for bfp in self.blendfile_paths:
if self.skip_path_check(bfp):
continue
bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
try:
with bpy.data.libraries.load(bfp, link=do_link) as (lib_in, lib_out):
if len(lib_in.collections):
if not self.args.is_quiet:
print(f"Trying to {operation_name} {bfp}/Collection/{lib_in.collections[0]}", flush=True)
lib_out.collections.append(lib_in.collections[0])
elif len(lib_in.objects):
if not self.args.is_quiet:
print(f"Trying to {operation_name} {bfp}/Object/{lib_in.objects[0]}", flush=True)
lib_out.objects.append(lib_in.objects[0])
except BaseException as e:
self.invalid_path_exception_process(bfp, e)
def test_link(self):
self.link_append(do_link=True)
def test_append(self):
self.link_append(do_link=False)
TESTS = (
TestBlendFileOpenAllTestFiles,
)
def argparse_create():
import argparse
# When --help or no args are given, print this help
description = ("Test basic versioning code by opening all blend files "
"in `tests/files` directory.")
parser = argparse.ArgumentParser(description=description)
parser.add_argument(
"--src-test-dir",
dest="src_test_dir",
default="..",
help="Root tests directory to search for blendfiles",
required=False,
)
parser.add_argument(
"--quiet",
dest="is_quiet",
type=bool,
default=False,
help="Whether to quiet prints of all blendfile read/link attempts",
required=False,
)
parser.add_argument(
"--slice-range",
dest="slice_range",
type=int,
default=1,
help="How many instances of this test are launched in parallel, the list of available blendfiles is then sliced "
"and each instance only processes the part matching its given `--slice-index`.",
required=False,
)
parser.add_argument(
"--slice-index",
dest="slice_index",
type=int,
default=0,
help="The index of the slice in blendfiles that this instance should process."
"Should always be specified when `--slice-range` > 1",
required=False,
)
return parser
def main():
args = argparse_create().parse_args()
assert args.slice_range > 0
assert 0 <= args.slice_index < args.slice_range
for Test in TESTS:
Test(args).run_all_tests()
if __name__ == '__main__':
import sys
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
main()