While individual modes have UI tests related to undo, this new set of tests in this new file is intended to be a set of very broad sanity tests that catch the most egregious errors that cause crashing on start up, whether due to python errors, UI rendering issues, or otherwise. Running these tests takes approximately 4 seconds currently as it adds and verifies the loading of each of the workspaces available "out of the box" to a blender user. Pull Request: https://projects.blender.org/blender/blender/pulls/139318
203 lines
5.6 KiB
Python
Executable File
203 lines
5.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-FileCopyrightText: 2019-2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
"""
|
|
Run interaction tests using event simulation.
|
|
|
|
Example usage from Blender's source dir:
|
|
|
|
This uses ``test_undo.py``, running the ``text_editor_simple`` function.
|
|
|
|
To run all tests:
|
|
|
|
./tests/python/ui_simulate/run.py --blender=blender.bin --tests '*'
|
|
|
|
For an editor to follow the tests:
|
|
|
|
./tests/python/ui_simulate/run.py --blender=blender.bin --tests '*' \
|
|
--step-command-pre='gvim --remote-silent +{line} "{file}"'
|
|
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
|
|
|
|
def create_parser():
|
|
import argparse
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__,
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
)
|
|
parser.add_argument(
|
|
"--blender",
|
|
dest="blender",
|
|
required=True,
|
|
metavar="BLENDER_COMMAND",
|
|
help="Location of the blender command to run (when quoted, may include arguments).",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--tests",
|
|
dest="tests",
|
|
nargs='+',
|
|
required=True,
|
|
metavar="TEST_ID",
|
|
help="Names of tests to run, use '*' to run all tests.",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--jobs", "-j",
|
|
dest="jobs",
|
|
default=1,
|
|
type=int,
|
|
help="Number of tests (and instances of Blender) to run in parallel.",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--keep-open",
|
|
dest="keep_open",
|
|
default=False,
|
|
action='store_true',
|
|
required=False,
|
|
help="Keep the Blender window open after running the test.",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--list-tests",
|
|
dest="list_tests",
|
|
default=False,
|
|
action='store_true',
|
|
required=False,
|
|
help="Show a list of available TEST_ID.",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--step-command-pre",
|
|
dest="step_command_pre",
|
|
required=False,
|
|
metavar="STEP_COMMAND_PRE",
|
|
help=(
|
|
"Command to run that takes the test file and line as arguments. "
|
|
"Literals {file} and {line} will be replaced with the file and line."
|
|
"Called for every event."
|
|
"Called for every event, allows an editor to track which commands run."
|
|
)
|
|
)
|
|
parser.add_argument(
|
|
"--step-command-post",
|
|
dest="step_command_post",
|
|
required=False,
|
|
metavar="STEP_COMMAND_POST",
|
|
help=(
|
|
"Command to run that takes the test file and line as arguments. "
|
|
"Literals {file} and {line} will be replaced with the file and line."
|
|
"Called for every event, allows an editor to track which commands run."
|
|
)
|
|
)
|
|
|
|
return parser
|
|
|
|
|
|
def all_test_ids(directory):
|
|
from types import FunctionType
|
|
for f in sorted(os.listdir(directory)):
|
|
if f.startswith("test_") and f.endswith(".py"):
|
|
mod = __import__(f[:-3])
|
|
for k, v in sorted(vars(mod).items()):
|
|
if not k.startswith("_") and isinstance(v, FunctionType):
|
|
yield f.rpartition(".")[0] + "." + k
|
|
|
|
|
|
def list_tests(directory):
|
|
for test_id in all_test_ids(directory):
|
|
print(test_id)
|
|
sys.exit(0)
|
|
|
|
|
|
def _process_test_id_fn(env, args, test_id):
|
|
import subprocess
|
|
import shlex
|
|
|
|
directory = os.path.dirname(__file__)
|
|
cmd = (
|
|
*shlex.split(args.blender),
|
|
"--enable-event-simulate",
|
|
"--factory-startup",
|
|
"--python", os.path.join(directory, "run_blender_setup.py"),
|
|
"--",
|
|
"--tests", test_id,
|
|
*(("--keep-open",) if args.keep_open else ()),
|
|
*(("--step-command-pre", args.step_command_pre) if args.step_command_pre else ()),
|
|
*(("--step-command-post", args.step_command_post) if args.step_command_post else ()),
|
|
)
|
|
callproc = subprocess.run(cmd, env=env)
|
|
return test_id, callproc.returncode == 0
|
|
|
|
|
|
def run(empty_user_dir):
|
|
directory = os.path.dirname(__file__)
|
|
if "--list-tests" in sys.argv:
|
|
list_tests(directory)
|
|
sys.exit(0)
|
|
|
|
if "bpy" in sys.modules:
|
|
raise Exception("Cannot run inside Blender")
|
|
|
|
parser = create_parser()
|
|
args = parser.parse_args()
|
|
|
|
tests = args.tests
|
|
|
|
# Validate tests exist
|
|
test_ids = list(all_test_ids(directory))
|
|
if tests[0] == "*":
|
|
tests = test_ids
|
|
else:
|
|
for test_id in tests:
|
|
if test_id not in test_ids:
|
|
print(test_id, "not found in", test_ids)
|
|
return
|
|
|
|
env = os.environ.copy()
|
|
env.update({
|
|
"LSAN_OPTIONS": "exitcode=0",
|
|
"BLENDER_USER_RESOURCES": empty_user_dir,
|
|
})
|
|
|
|
# We could support multiple tests per Blender session.
|
|
results = []
|
|
results_fail = 0
|
|
if args.jobs <= 1:
|
|
for test_id in tests:
|
|
_, success = _process_test_id_fn(env, args, test_id)
|
|
results.append((test_id, success))
|
|
if not success:
|
|
results_fail += 1
|
|
else:
|
|
from concurrent.futures import ProcessPoolExecutor
|
|
executor = ProcessPoolExecutor(max_workers=args.jobs)
|
|
num_tests = len(tests)
|
|
for test_id, success in executor.map(_process_test_id_fn, (env,) * num_tests, (args,) * num_tests, tests):
|
|
results.append((test_id, success))
|
|
if not success:
|
|
results_fail += 1
|
|
|
|
print(len(results), "tests,", results_fail, "failed")
|
|
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()
|