blender-test-data/ui_simulate/run.py

194 lines
5.4 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Run interaction tests using event simulation.
Example usage from Blender's source dir:
../lib/tests/ui_simulate/run.py --blender=./blender.bin --tests test_undo.text_editor_simple
This uses ``test_undo.py``, running the ``text_editor_simple`` function.
To run all tests:
../lib/tests/ui_simulate/run.py --blender=blender.bin --tests '*'
For an editor to follow the tests:
../lib/tests/ui_simulate/run.py --blender=blender.bin --tests '*' \
--step-command-pre='gvim --remote-silent +{line} "{file}"'
"""
# from modules import easy_keys
import os
import sys
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 main():
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",
})
# 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)
if __name__ == "__main__":
main()