Skip to content

Commit 08ef9f2

Browse files
committed
Add fixtures and improve Blender subprocess isolation
1 parent 6cf6b69 commit 08ef9f2

File tree

8 files changed

+209
-37
lines changed

8 files changed

+209
-37
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.0.2
2+
current_version = 0.0.3
33

44
[bumpversion:file:pytest_blender/__init__.py]
55

README.md

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ Blender in headless mode using its builtin Python interpreter.
1414
pip install pytest-blender
1515
```
1616

17-
## Usage
17+
## Documentation
1818

19-
### Install dependencies in Blender Python interpreter
19+
### Usage
20+
21+
#### Install dependencies in Blender Python interpreter
2022

2123
Before execute it, you need to install your testing dependencies inside the
2224
builtin Blender Python interpreter. To get the interpreter location you can
@@ -35,7 +37,7 @@ with `--blender-executable` option:
3537
pytest-blender --blender-executable ~/blender-2.91.2-linux64/blender
3638
```
3739

38-
### Execute tests
40+
#### Execute tests
3941

4042
After installing dependencies, just call pytest as usually.
4143

@@ -52,7 +54,7 @@ cachedir: .pytest_cache
5254
rootdir: /home/mondeja/files/code/pytest-blender
5355
collected 1 item
5456
55-
tests/test_import_bpy.py::test_inside_blender <module 'bpy' from '/usr/share/blender/scripts/modules/bpy/__init__.py'>
57+
tests/test_bpy_import.py::test_inside_blender <module 'bpy' from '/usr/share/blender/scripts/modules/bpy/__init__.py'>
5658
PASSED
5759
=========================== 1 passed in 0.01s ==================================
5860
```
@@ -73,11 +75,34 @@ platform linux -- Python 3.7.7, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
7375
rootdir: ~/pytest-blender
7476
collected 1 item
7577
76-
tests/test_import_bpy.py . [100%]
78+
tests/test_bpy_import.py . [100%]
7779
7880
============================== 1 passed in 0.00s ===============================
7981
```
8082

83+
### Reference
84+
85+
#### Fixtures
86+
87+
<a name="blender_executable" href="#blender_executable">#</a> <b>blender_executable</b> ⇒ `str`
88+
89+
Returns the path of the executable that has started the current Blender session.
90+
91+
<a name="blender_version" href="#blender_version">#</a> <b>blender_version</b> ⇒ `str`
92+
93+
Returns the version of Blender running in the current session.
94+
95+
<a name="blender_python_executable" href="#blender_python_executable">#</a> <b>blender_python_executable</b> ⇒ `str`
96+
97+
Returns the path of the Python executable builtin in the Blender release of the
98+
currently running session.
99+
100+
<a name="blender_python_version" href="#blender_python_version">#</a> <b>blender_python_version</b> ⇒ `str`
101+
102+
Returns the version of the Python executable builtin in the Blender release of
103+
the currently running session.
104+
105+
81106
[pypi-link]: https://pypi.org/project/pytest-blender
82107
[pypi-version-badge-link]: https://img.shields.io/pypi/v/pytest-blender
83108
[pypi-pyversions-badge-link]: https://img.shields.io/pypi/pyversions/pytest-blender

pytest_blender/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.2"
1+
__version__ = "0.0.3"

pytest_blender/__main__.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import argparse
2-
import os
3-
import subprocess
42
import sys
53

64
from pytest_blender import __version__
5+
from pytest_blender.run_pytest import get_blender_binary_path_python
76
from pytest_blender.utils import which_blender_by_os
87

98

@@ -46,22 +45,7 @@ def parse_args(args):
4645

4746
def run(args):
4847
opts = parse_args(args)
49-
50-
stdout = subprocess.check_output(
51-
[
52-
opts.blender_executable,
53-
"-b",
54-
"--python-expr",
55-
"import bpy;print(bpy.app.binary_path_python)",
56-
]
57-
)
58-
59-
blender_python_path = None
60-
for line in stdout.decode("utf-8").splitlines():
61-
if line.startswith(os.sep):
62-
blender_python_path = line
63-
break
64-
sys.stdout.write(f"{blender_python_path}\n")
48+
sys.stdout.write(f"{get_blender_binary_path_python(opts.blender_executable)}\n")
6549

6650
return 0
6751

pytest_blender/plugin.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,21 @@ def pytest_addoption(parser):
1919
)
2020

2121

22-
@pytest.hookimpl(tryfirst=True)
23-
def pytest_configure(config):
24-
# parse blender executable location
22+
def _get_blender_executable(config):
2523
blender_executable = config.getoption("--blender-executable")
2624
if hasattr(blender_executable, "__call__"):
27-
blender_executable = blender_executable()
28-
else:
29-
blender_executable = blender_executable[0]
25+
return blender_executable()
26+
return blender_executable[0]
27+
28+
29+
@pytest.fixture
30+
def get_blender_version():
31+
stdout = subprocess.check_output([_get_blender_executable(), "--version"])
32+
return stdout.splitlines()[0].split(" ")[1]
33+
3034

35+
@pytest.hookimpl(tryfirst=True)
36+
def pytest_configure(config):
3137
# build propagated CLI args
3238
propagated_cli_args = []
3339
_inside_root_invocation_arg = False
@@ -40,6 +46,8 @@ def pytest_configure(config):
4046
continue
4147
propagated_cli_args.append(arg)
4248

49+
blender_executable = _get_blender_executable(config)
50+
4351
# run pytest using blender
4452
proc = subprocess.Popen(
4553
[
@@ -51,6 +59,8 @@ def pytest_configure(config):
5159
"run_pytest.py",
5260
),
5361
"--",
62+
"--pytest-blender-executable",
63+
blender_executable,
5464
*propagated_cli_args, # propagate command line arguments
5565
],
5666
stdout=sys.stdout,

pytest_blender/run_pytest.py

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1+
"""Script ran by ``plugin.py`` module using a builtin Blender Python interpreter.
2+
3+
Keep in mind that may be some functions here that, although are used in other
4+
parts of the package like ``get_blender_binary_path_python``, can't be located
5+
outside this module because the installation of `pytest-blender` in the Blender
6+
builtin Python intepreter shouldn't be mandatory, so avoid
7+
``from pytest_blender...`` imports in this module.
8+
"""
9+
10+
import os
111
import shlex
12+
import subprocess
213
import sys
314

415
import pytest
@@ -13,9 +24,113 @@ def _join(value):
1324
return join(value)
1425

1526

27+
# this function can't be in 'utils'
28+
def get_blender_binary_path_python(blender_executable):
29+
stdout = subprocess.check_output(
30+
[
31+
blender_executable,
32+
"-b",
33+
"--python-expr",
34+
"import bpy;print(bpy.app.binary_path_python)",
35+
]
36+
)
37+
38+
blender_python_path = None
39+
for line in stdout.decode("utf-8").splitlines():
40+
if line.startswith(os.sep):
41+
blender_python_path = line
42+
break
43+
return blender_python_path
44+
45+
46+
def main():
47+
raw_argv = shlex.split(_join(sys.argv).split(" -- ")[1:][0])
48+
49+
# disable self-propagation, if installed in Blender Python interpreter
50+
argv = ["-p", "no:pytest-blender"]
51+
52+
# parse Blender executable location, propagated from hook
53+
blender_executable, _inside_bexec_arg = (None, None)
54+
for arg in raw_argv:
55+
if arg == "--pytest-blender-executable":
56+
_inside_bexec_arg = True
57+
continue
58+
elif _inside_bexec_arg:
59+
blender_executable = arg
60+
_inside_bexec_arg = False
61+
continue
62+
argv.append(arg)
63+
64+
class PytestBlenderPlugin:
65+
def _blender_python_executable(self, request):
66+
blender_python_executable = request.config.cache.get(
67+
"pytest-blender/blender-python-executable",
68+
None,
69+
)
70+
if blender_python_executable is None:
71+
blender_python_executable = get_blender_binary_path_python(
72+
blender_executable,
73+
)
74+
request.config.cache.set(
75+
"pytest-blender/blender-python-executable",
76+
blender_python_executable,
77+
)
78+
return blender_python_executable
79+
80+
@pytest.fixture
81+
def blender_executable(self):
82+
return blender_executable
83+
84+
@pytest.fixture
85+
def blender_python_executable(self, request):
86+
return self._blender_python_executable(request)
87+
88+
@pytest.fixture
89+
def blender_version(self, request):
90+
blender_version = request.config.cache.get(
91+
"pytest-blender/blender-version",
92+
None,
93+
)
94+
if blender_version is None:
95+
version_stdout = subprocess.check_output(
96+
[blender_executable, "--version"]
97+
)
98+
blender_version = (
99+
version_stdout.decode("utf-8").splitlines()[0].split(" ")[1]
100+
)
101+
request.config.cache.set(
102+
"pytest-blender/blender-version",
103+
blender_version,
104+
)
105+
return blender_version
106+
107+
@pytest.fixture
108+
def blender_python_version(self, request):
109+
blender_python_version = request.config.cache.get(
110+
"pytest-blender/blender-python-version",
111+
None,
112+
)
113+
if blender_python_version is None:
114+
blender_python_executable = self._blender_python_executable(request)
115+
blender_python_version_stdout = subprocess.check_output(
116+
[
117+
blender_python_executable,
118+
"--version",
119+
]
120+
)
121+
blender_python_version = (
122+
blender_python_version_stdout.decode("utf-8")
123+
.splitlines()[0]
124+
.split(" ")[1]
125+
)
126+
request.config.cache.set(
127+
"pytest-blender/blender-python-version",
128+
blender_python_version,
129+
)
130+
return blender_python_version
131+
132+
return pytest.main(argv, plugins=[PytestBlenderPlugin()])
133+
134+
16135
if __name__ == "__main__":
17-
if sys.argv[-1] == "--":
18-
argv = []
19-
else:
20-
argv = shlex.split(_join(sys.argv).split(" -- ")[1:][0])
21-
sys.exit(pytest.main(argv))
136+
sys.exit(main())

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = pytest_blender
3-
version = 0.0.2
3+
version = 0.0.3
44
description = Blender addons tester for Pytest.
55
long_description = file: README.md
66
long_description_content_type = text/markdown

tests/test_fixtures.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""pytest-blender fixtures tests."""
2+
3+
import os
4+
import re
5+
import subprocess
6+
7+
8+
def test_blender_executable(blender_executable):
9+
assert os.path.isfile(blender_executable)
10+
assert "blender" in os.path.basename(blender_executable).lower()
11+
12+
13+
def test_blender_python_executable(blender_python_executable):
14+
assert os.path.isfile(blender_python_executable)
15+
assert "python" in os.path.basename(blender_python_executable)
16+
17+
18+
def test_blender_version(blender_executable, blender_version):
19+
stdout = subprocess.check_output([blender_executable, "--version"])
20+
expected_blender_version = stdout.decode("utf-8").splitlines()[0].split(" ")[1]
21+
22+
assert expected_blender_version == blender_version
23+
assert re.match(r"\d+\.\d", blender_version)
24+
25+
26+
def test_blender_python_version(blender_python_version, blender_python_executable):
27+
blender_python_version_stdout = subprocess.check_output(
28+
[
29+
blender_python_executable,
30+
"--version",
31+
]
32+
)
33+
expected_blender_python_version = (
34+
blender_python_version_stdout.decode("utf-8").splitlines()[0].split(" ")[1]
35+
)
36+
37+
assert blender_python_version == expected_blender_python_version
38+
assert re.match(r"\d+\.\d\.?\d*", blender_python_version)

0 commit comments

Comments
 (0)