Skip to content

Commit

Permalink
v1.0.2 bugfix release (#38)
Browse files Browse the repository at this point in the history
- Don't show ugly traceback executing `pytest --help`.
- Don't show debug output retrieving Python Blender location with `pytest-blender` CLI.
- Avoid to display a warning retrieving Python Blender location in Blender >= 2.91.
- Improve Blender executable path discovering in MacOS
  • Loading branch information
mondeja committed Oct 26, 2021
1 parent 7fcf0ab commit f5caea6
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.0.1
current_version = 1.0.2

[bumpversion:file:pytest_blender/__init__.py]

Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- ubuntu-latest
- macos-latest
blender-version:
- '2.93.4'
- '2.93.5'
- '2.92.0'
- '2.91.2'
- '2.90.1'
Expand Down Expand Up @@ -61,8 +61,12 @@ jobs:
$PYTHON_BLENDER_EXECUTABLE -m ensurepip
$PYTHON_BLENDER_EXECUTABLE -m pip install pytest
echo "::set-output name=blender-executable::$BLENDER_EXECUTABLE_PATH"
- name: Test with pytest
- name: Test with Blender Python executable (no cache)
run: pytest -svv -p no:cacheprovider --blender-executable "${{ steps.install-dependencies.outputs.blender-executable }}" tests
- name: Test with Blender Python executable
run: pytest -svv --blender-executable "${{ steps.install-dependencies.outputs.blender-executable }}" tests
- name: Test with local Python executable
run: BLENDER_EXECUTABLE="${{ steps.install-dependencies.outputs.blender-executable }}" pytest -svv -p no:pytest-blender tests

build-sdist:
if: startsWith(github.ref, 'refs/tags/')
Expand Down
2 changes: 1 addition & 1 deletion pytest_blender/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.0.1"
__version__ = "1.0.2"
3 changes: 2 additions & 1 deletion pytest_blender/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def parse_args(args):

def run(args):
opts = parse_args(args)
sys.stdout.write(f"{get_blender_binary_path_python(opts.blender_executable)}\n")
blender_python = get_blender_binary_path_python(opts.blender_executable)
sys.stdout.write(f"{blender_python}\n")

return 0

Expand Down
8 changes: 8 additions & 0 deletions pytest_blender/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def _get_blender_executable(config):

@pytest.hookimpl(tryfirst=True)
def pytest_configure(config):
pytest_help_opt = False

# build propagated CLI args
propagated_cli_args = []
_inside_root_invocation_arg = False
Expand All @@ -38,8 +40,14 @@ def pytest_configure(config):
elif arg == "--blender-executable":
_inside_root_invocation_arg = True
continue
elif arg in ["-h", "--help"]:
pytest_help_opt = True
break
propagated_cli_args.append(arg)

if pytest_help_opt:
return

blender_executable = _get_blender_executable(config)

if not blender_executable:
Expand Down
94 changes: 60 additions & 34 deletions pytest_blender/run_pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ def _join(value):
return join(value)


# this function can't be in 'utils'
def get_blender_binary_path_python(blender_executable):
def _parse_version(version_string):
return tuple(int(i) for i in version_string.split(".") if i.isdigit())


def get_blender_binary_path_python(blender_executable, blend_version=None):
"""Get Blender's Python executable location.
This function can't be in utils because the module is not loaded from
Expand All @@ -42,13 +45,22 @@ def get_blender_binary_path_python(blender_executable):
str: Blender's Python executable path.
"""
if blend_version is None:
blend_version = get_blender_version(blender_executable)
python_expr = (
"import sys;print(sys.executable);"
if _parse_version(blend_version) >= (2, 91)
else "import bpy;print(bpy.app.binary_path_python)"
)

stdout = subprocess.check_output(
[
blender_executable,
"-b",
"--python-expr",
"import bpy;print(bpy.app.binary_path_python)",
]
python_expr,
],
stderr=subprocess.STDOUT,
)

blender_python_path = None
Expand All @@ -59,20 +71,35 @@ def get_blender_binary_path_python(blender_executable):
return blender_python_path


def get_blender_version(blender_executable):
"""Get Blender's version goving its executable location.
blender_executable : str
Blender's executable location.
Returns
-------
str: Blender's version.
"""
version_stdout = subprocess.check_output([blender_executable, "--version"])
return version_stdout.decode("utf-8").splitlines()[0].split(" ")[1]


def main():
raw_argv = shlex.split(_join(sys.argv).split(" -- ")[1:][0])

# disable self-propagation, if installed in Blender Python interpreter
argv = ["-p", "no:pytest-blender"]

# parse Blender executable location, propagated from hook
blender_executable, _inside_bexec_arg = (None, None)
_blender_executable, _inside_bexec_arg = (None, None)
for arg in raw_argv:
if arg == "--pytest-blender-executable":
_inside_bexec_arg = True
continue
elif _inside_bexec_arg:
blender_executable = arg
_blender_executable = arg
_inside_bexec_arg = False
continue
argv.append(arg)
Expand All @@ -84,92 +111,91 @@ class PytestBlenderPlugin:
"""

def _blender_python_executable(self, request):
blender_python_executable = None
response = None

# if pytest is executed without cache provider, the 'cache'
# attribute will not be available (-p no:cacheprovider)
if hasattr(request.config, "cache"):
blender_python_executable = request.config.cache.get(
response = request.config.cache.get(
"pytest-blender/blender-python-executable",
None,
)

if blender_python_executable is None:
blender_python_executable = get_blender_binary_path_python(
blender_executable,
if response is None:
response = get_blender_binary_path_python(
_blender_executable,
blend_version=self._blender_version(request),
)
if hasattr(request.config, "cache"):
request.config.cache.set(
"pytest-blender/blender-python-executable",
blender_python_executable,
response,
)
return blender_python_executable
return response

@pytest.fixture
def blender_executable(self):
"""Get the executable of the current Blender's session."""
return blender_executable
return _blender_executable

@pytest.fixture
def blender_python_executable(self, request):
"""Get the executable of the current Blender's Python session."""
return self._blender_python_executable(request)

@pytest.fixture
def blender_version(self, request):
"""Get the Blender version of the current session."""
blender_version = None
def _blender_version(self, request):
response = None

if hasattr(request.config, "cache"):
blender_version = request.config.cache.get(
response = request.config.cache.get(
"pytest-blender/blender-version",
None,
)

if blender_version is None:
version_stdout = subprocess.check_output(
[blender_executable, "--version"]
)
blender_version = (
version_stdout.decode("utf-8").splitlines()[0].split(" ")[1]
)
if response is None:
response = get_blender_version(_blender_executable)
if hasattr(request.config, "cache"):
request.config.cache.set(
"pytest-blender/blender-version",
blender_version,
response,
)
return blender_version
return response

@pytest.fixture
def blender_version(self, request):
"""Get the Blender version of the current session."""
return self._blender_version(request)

@pytest.fixture
def blender_python_version(self, request):
"""Get the version of the Blender's Python executable."""
blender_python_version = None
response = None

if hasattr(request.config, "cache"):
blender_python_version = request.config.cache.get(
response = request.config.cache.get(
"pytest-blender/blender-python-version",
None,
)

if blender_python_version is None:
if response is None:
blender_python_executable = self._blender_python_executable(request)
blender_python_version_stdout = subprocess.check_output(
[
blender_python_executable,
"--version",
]
)
blender_python_version = (
response = (
blender_python_version_stdout.decode("utf-8")
.splitlines()[0]
.split(" ")[1]
)
if hasattr(request.config, "cache"):
request.config.cache.set(
"pytest-blender/blender-python-version",
blender_python_version,
response,
)
return blender_python_version
return response

@pytest.fixture(scope="session")
def install_addons_from_dir(self, request):
Expand Down
5 changes: 5 additions & 0 deletions pytest_blender/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import sys


pytest_blender_unactive = "-p" in sys.argv and "no:pytest-blender" in sys.argv
pytest_blender_active = not pytest_blender_unactive
8 changes: 5 additions & 3 deletions pytest_blender/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

def which_blender_by_os():
"""Get the expected Blender executable location by operative system."""
if sys.platform == "darwin":
return "Blender"
return "blender.exe" if "win" in sys.platform else shutil.which("blender")
return (
shutil.which("Blender")
if sys.platform == "darwin"
else ("blender.exe" if "win" in sys.platform else shutil.which("blender"))
)
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = pytest_blender
version = 1.0.1
version = 1.0.2
description = Blender Pytest plugin.
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down
32 changes: 20 additions & 12 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,26 @@
import pytest


@pytest.fixture(scope="session", autouse=True)
def _register_addons(request, install_addons_from_dir, disable_addons):
addons_dir = os.path.join(
os.path.abspath(os.path.dirname(__file__)),
"addons",
)
addon_module_names = ["pytest_blender_basic"]
try:
from pytest_blender.test import pytest_blender_active
except ImportError:
# executing pytest from Python Blender executable, the plugin is active
pytest_blender_active = True

f = io.StringIO()
with redirect_stdout(f):
install_addons_from_dir(addons_dir, addon_module_names)
if pytest_blender_active:

yield
@pytest.fixture(scope="session", autouse=True)
def _register_addons(request, install_addons_from_dir, disable_addons):
addons_dir = os.path.join(
os.path.abspath(os.path.dirname(__file__)),
"addons",
)
addon_module_names = ["pytest_blender_basic"]

disable_addons(addon_module_names)
f = io.StringIO()
with redirect_stdout(f):
install_addons_from_dir(addons_dir, addon_module_names)

yield

disable_addons(addon_module_names)
16 changes: 14 additions & 2 deletions tests/test_addons.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
"""Example addons testing."""

import _bpy
import addon_utils
import pytest


try:
from pytest_blender.test import pytest_blender_unactive
except ImportError:
pytest_blender_unactive = False


@pytest.mark.skipif(
pytest_blender_unactive,
reason="Requires testing loading the pytest-blender plugin.",
)
def test_basic_addon():
import _bpy
import addon_utils

_module_loaded = False
for addon_module in addon_utils.modules():
if addon_module.__name__ == "pytest_blender_basic":
Expand Down
13 changes: 13 additions & 0 deletions tests/test_bpy_import.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
import pytest


try:
from pytest_blender.test import pytest_blender_unactive
except ImportError:
pytest_blender_unactive = False


@pytest.mark.skipif(
pytest_blender_unactive,
reason="Requires testing loading the pytest-blender plugin.",
)
def test_bpy_import():
import bpy # noqa F401
Loading

0 comments on commit f5caea6

Please sign in to comment.