Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions ggmolvis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
import shutil
import tempfile
import uuid
from importlib.metadata import version
import bpy
from bpy.app.handlers import frame_change_pre
Expand Down Expand Up @@ -45,7 +46,8 @@
#count += 1

# we will save the current session to the temporary directory
dest_dir = tempfile.gettempdir()
dest_dir = f"{tempfile.gettempdir()}/{uuid.uuid4()}"
os.makedirs(dest_dir, exist_ok=True)
dest_path = os.path.join(dest_dir, base_name)
logger.debug(f"Blend file stored at {dest_path}")

Expand Down Expand Up @@ -84,4 +86,4 @@ def cleanup_function():
except Exception as e:
print(e)

atexit.register(cleanup_function)
atexit.register(cleanup_function)
15 changes: 14 additions & 1 deletion ggmolvis/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,22 @@
# More information at
# https://docs.pytest.org/en/stable/how-to/fixtures.html#scope-sharing-fixtures-across-classes-modules-packages-or-session

import os
import pytest

@pytest.fixture
def ggmv():
from ggmolvis import GGMolVis
return GGMolVis()
return GGMolVis()


def pytest_runtest_setup(item):
mark = item.get_closest_marker("xslow")
if mark is not None:
try:
v = int(os.environ.get('GGMOLVIS_XSLOW', '0'))
except ValueError:
v = False
if not v:
pytest.skip("very slow test; "
"set environment variable GGMOLVIS_XSLOW=1 to run it")
29 changes: 29 additions & 0 deletions ggmolvis/tests/test_render.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from ggmolvis.ggmolvis import GGMolVis
from ggmolvis.tests.data import PSF, DCD
from MDAnalysisTests.datafiles import GRO
from PIL import Image
import numpy as np

from MDAnalysis import Universe
from ggmolvis.utils import MOL_AVAILABLE_STYLES, AVAILABLE_MATERIALS
Expand Down Expand Up @@ -76,3 +79,29 @@ def test_render_error_frame_range(ggmv, atomgroup, render_mp4):
ggmv.render(mol, frame_range=(0, 4),
resolution=(300, 200),
filepath=render_mp4)


@cleanup
@pytest.mark.xslow
def test_render_basic_bg(tmpdir, ggmv):
# confirm correct propagation of background color
# and image size specified
u = Universe(GRO)
mol = ggmv.molecule(u.atoms)
with tmpdir.as_cwd():
ggmv.render(object=mol,
resolution=(50, 50),
filepath=f"test_red.png",
mode="image",
# red background
composite_bg_rgba=(0.9, 0, 0, 1.0),
lens=35)
with Image.open("test_red.png") as img:
img_array = np.array(img)
red = img_array[..., 0]
green = img_array[..., 1]
blue = img_array[..., 2]
# red should be the dominant channel
assert red.sum() > (green.sum() + blue.sum())
# image size should be 50 x 50 x 4 channels
assert img_array.size == 10_000
Copy link
Contributor Author

@tylerjereddy tylerjereddy Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on our last call I was under the impression that neither ggmolvis nor MolecularNodes did render testing in GHA CI, but after investigating on my fork (tylerjereddy#1), it became clear that ggmolvis has a small number of render tests. They don't use composition, and no assertions are made about the correctness of their results, so they basically just sniff test that there is no crash.

From the durations output over there:

============================= slowest 10 durations =============================
93.19s call     ggmolvis/tests/test_molecules.py::test_render[render_options1]
16.77s call     ggmolvis/tests/test_molecules.py::test_render_scene
16.25s call     ggmolvis/tests/test_molecules.py::test_render[render_options2]
16.05s call     ggmolvis/tests/test_molecules.py::test_render[render_options0]
10.78s call     ggmolvis/tests/test_render.py::test_render_basic_bg
<snip>

In that case I temporarily disabled the composition in my new test here so that it would run (i.e., temporarily turned off composite_bg_rgba).

If I check the durations locally on this branch via GGMOLVIS_XSLOW=1 python -m pytest -n 8 --durations=10

======================================================================================================================== slowest 10 durations ========================================================================================================================
3.65s call     ggmolvis/tests/test_molecules.py::test_render[render_options1]
2.03s call     ggmolvis/tests/test_render.py::test_render_basic_bg
1.84s call     ggmolvis/tests/test_molecules.py::test_render_scene
1.68s call     ggmolvis/tests/test_molecules.py::test_render[render_options0]
1.52s call     ggmolvis/tests/test_molecules.py::test_render[render_options2]
0.36s call     ggmolvis/tests/test_molecules.py::test_show_molecule_with_style[cartoon]
0.22s call     ggmolvis/tests/test_molecules.py::test_show_molecule_with_style[preset_2]
<snip>

Also, unrelated, but looks like the exit segfault is indeed gone locally now with bpy 4.4.0 released 3 days ago.

3 changes: 2 additions & 1 deletion ggmolvis/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ def _clean_up(ggmv):
ggmv._session.remove(artist.trajectory.uuid)
except Exception:
pass
bpy.ops.wm.open_mainfile(filepath=bpy.data.filepath)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added based on the discussion at gh-41, and seems to help isolate (from cross-test pollution) the scenes used in render tests locally. It is slightly different than the approach Brady uses upstream at https://github.com/BradyAJohnston/MolecularNodes/blob/main/tests/conftest.py#L16 with bpy.ops.wm.read_homefile(app_template=""). When I used the latter, I ran into a failure with test_show_trajectory_with_material. Probably some preservation of state is needed that Yuxuan understands and I don't?

One problem with this change is that, at least in local testing, this seems to reduce "thread safety" with pytest-xdist a bit. For example, the following incantation sometimes has issues parsing .blend files:

GGMOLVIS_XSLOW=1 python -m pytest -n 8 (to reproduce locally on this branch, it can help to increase -n to something like 32)

Details ``` =============================================================================================================================== ERRORS =============================================================================================================================== __________________________________________________________________________________________________________________ ERROR collecting ggmolvis/tests ___________________________________________________________________________________________________________________ ../spack/opt/spack/darwin-sonoma-m1/apple-clang-15.0.0/python-3.11.7-g6qcoukv4k4emoygj4zxqsig2zqs3wlj/lib/python3.11/importlib/__init__.py:126: in import_module return _bootstrap._gcd_import(name[level:], package, level) :1204: in _gcd_import ??? :1176: in _find_and_load ??? :1126: in _find_and_load_unlocked ??? :241: in _call_with_frames_removed ??? :1204: in _gcd_import ??? :1176: in _find_and_load ??? :1126: in _find_and_load_unlocked ??? :241: in _call_with_frames_removed ??? :1204: in _gcd_import ??? :1176: in _find_and_load ??? :1147: in _find_and_load_unlocked ??? :690: in _load_unlocked ??? :940: in exec_module ??? :241: in _call_with_frames_removed ??? ggmolvis/__init__.py:55: in bpy.ops.wm.open_mainfile(filepath=dest_path) ../../python_venvs/py_311_mda_dev/lib/python3.11/site-packages/bpy/4.4/scripts/modules/bpy/ops.py:109: in __call__ ret = _op_call(self.idname_py(), kw) E RuntimeError: Error: File format is not supported in file "/var/folders/5_/hm0ft57n6dn2ksgg2p0bx5h0000w2g/T/ggmolvis.blend" ```

Spot checking the ubuntu-latest main CI job, I see the testsuite finishing with 38 passed, 1 skipped, 3 xfailed, 8 warnings in 51.18s, which suggests that the new xslow test is correctly being skipped in the CI.

That said, I'm not sure where we stand on the tradeoffs here if this reduces thread safety. Maybe we could use a lock/mutex somehow, or even just suggest avoiding pytest-xdist in the short term. IIRC I can even observe this type of failure without pytest-xdist usage, but it is much rarer.

ggmv._session.prune()
ggmv._session._ggmolvis = set()
ggmv._initialized = False
ggmv = GGMolVis()
ggmv = GGMolVis()
4 changes: 4 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]

markers =
xslow: mark test as extremely slow (not run unless explicitly requested)
Loading