File: run.py

package info (click to toggle)
blender 4.3.2%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 309,564 kB
  • sloc: cpp: 2,385,210; python: 330,236; ansic: 280,972; xml: 2,446; sh: 972; javascript: 317; makefile: 170
file content (193 lines) | stat: -rwxr-xr-x 5,481 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#!/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


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()