Repository: invesalius/invesalius3
Pull request #922: Add GitHub Actions Workflow for Automated Pytest testing
Author: login: n33lp
Created: 2025-03-20T18:09:30Z | Merged: 2025-04-01T21:06:23Z

This pull request includes several improvements and additions to the project, focusing on setting up CI workflows, enhancing session handling, and adding tests for math utilities. The most important changes include the addition of GitHub Actions workflows for caching and testing, modifications to session handling to ensure directory creation, and the addition of comprehensive tests for math utilities.

### CI Workflows

* [`.github/workflows/cache.yml`](diffhunk://#diff-f5215bed3ca2aa882b75e728bfe2ae3725f031ceb1246c8d5c25947e706e90f2R1-R63): Added a new workflow to build and cache dependencies using `uv`. This workflow runs on pushes to the `master` branch.
* [`.github/workflows/tests.yml`](diffhunk://#diff-1db27d93186e46d3b441ece35801b244db8ee144ff1405ca27a163bfe878957fR1-R74): Added a new workflow to run tests using `pytest` with `xvfb` for graphical tests. This workflow runs on pull requests to the `master` branch.

### Session Handling

* [`invesalius/session.py`](diffhunk://#diff-3e002dacefbcf9b009b869737cbec55705aa27d255962c8007e0fb4033e25d97L190-R190): Modified the `_write_to_json` method to ensure the parent directory exists before writing the JSON file.

### Tests

* [`tests/test_math_utils.py`](diffhunk://#diff-a412808f8be92bd269374dc91ca553982301a05096f50a9b7dd95570b009bd27R1-R56): Added new tests for various math utility functions, including distance calculation, angle calculation, ellipse area and circumference, polygon area and perimeter, and inner product calculations.
* [`tests/test_session.py`](diffhunk://#diff-3121d1f70912305d7e409dfd557f34d9f5869a86d8d347b0a4cca1e1c124df42R24-R28): Updated session tests to mock directory creation using `pathlib.Path.mkdir`. [[1]](diffhunk://#diff-3121d1f70912305d7e409dfd557f34d9f5869a86d8d347b0a4cca1e1c124df42R24-R28) [[2]](diffhunk://#diff-3121d1f70912305d7e409dfd557f34d9f5869a86d8d347b0a4cca1e1c124df42R72-R88)

### Miscellaneous

* [`CMakeLists.txt`](diffhunk://#diff-1e7de1ae2d059d21e1dd75d5812d5a34b0222cef273b7c3a2af62eb747f9d20aR7-R9): Added a conditional check to include the `plugins` directory if it exists.
* [`invesalius/session.py`](diffhunk://#diff-3e002dacefbcf9b009b869737cbec55705aa27d255962c8007e0fb4033e25d97R26-L35): Removed unnecessary `TYPE_CHECKING` import for `Path`.

Prt of GSOC'25 application


Diff:
diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml
new file mode 100644
index 000000000..63d47cf1b
--- /dev/null
+++ b/.github/workflows/cache.yml
@@ -0,0 +1,58 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - uses: astral-sh/setup-uv@v5
+        with:
+          python-version: 3.12
+          enable-cache: true
+          cache-dependency-glob: "uv.lock"
+
+      - name: Syncing with UV
+        run: uv sync
+
+      - name: Minimize uv cache
+        run: uv cache prune --ci
+
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 000000000..aa2ba2eb8
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,69 @@
+name: tests
+
+on:
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  run-tests:
+    runs-on: ubuntu-22.04 
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0  
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+
+      - uses: astral-sh/setup-uv@v5
+        with:
+          python-version: 3.12
+          enable-cache: true
+          cache-dependency-glob: "uv.lock"
+
+      - name: Syncing with UV
+        run: uv sync
+
+      - name: Run tests with xvfb
+        run: xvfb-run -a bash -c "pytest"
+
+      - name: Minimize uv cache
+        run: uv cache prune --ci
+
+
+  combined-checks:
+    runs-on: ubuntu-22.04
+    needs: [run-tests]
+    steps:
+      - run: echo "All checks passed! Ready to merge."
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5c00584ab..37e72c431 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,4 +4,6 @@ project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION} LANGUAGES C C
 add_definitions(-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION)
 
 add_subdirectory(invesalius_cy)
-add_subdirectory(plugins)
+if(EXISTS "./plugins/CMakeLists.txt")
+    add_subdirectory(plugins)
+endif()
diff --git a/invesalius/session.py b/invesalius/session.py
index 2d7e003bd..206a7c585 100644
--- a/invesalius/session.py
+++ b/invesalius/session.py
@@ -23,6 +23,7 @@
 import json
 import os
 from json.decoder import JSONDecodeError
+from pathlib import Path
 from random import randint
 from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Union
 
@@ -30,9 +31,6 @@
 from invesalius.pubsub import pub as Publisher
 from invesalius.utils import Singleton, debug, deep_merge_dict
 
-if TYPE_CHECKING:
-    from pathlib import Path
-
 CONFIG_PATH = os.path.join(inv_paths.USER_INV_DIR, "config.json")
 OLD_CONFIG_PATH = os.path.join(inv_paths.USER_INV_DIR, "config.cfg")
 
@@ -187,7 +185,9 @@ def WriteStateFile(self) -> None:
         self._write_to_json(self._state, STATE_PATH)
 
     def _write_to_json(self, config_dict: dict, config_filename: "str | Path") -> None:
-        with open(config_filename, "w") as config_file:
+        config_path = Path(config_filename)
+        config_path.parent.mkdir(parents=True, exist_ok=True)  # Ensure parent dir exists
+        with open(config_path, "w") as config_file:
             json.dump(config_dict, config_file, sort_keys=True, indent=4)
 
     def _add_to_recent_projects(self, item: Tuple[str, str]) -> None:
diff --git a/tests/test_math_utils.py b/tests/test_math_utils.py
new file mode 100644
index 000000000..03e27d221
--- /dev/null
+++ b/tests/test_math_utils.py
@@ -0,0 +1,56 @@
+from math import isclose
+
+import numpy as np
+import pytest
+
+from invesalius.math_utils import (
+    calc_ellipse_area,
+    calc_ellipse_circumference,
+    calc_polygon_area,
+    calc_polygon_perimeter,
+    calculate_angle,
+    calculate_distance,
+    inner1d,
+)
+
+
+def test_calculate_distance():
+    assert calculate_distance((0, 0), (1, 0)) == pytest.approx(1.0)
+    assert calculate_distance((0, 0), (0, 1)) == pytest.approx(1.0)
+    assert calculate_distance((0, 0), (3, 4)) == pytest.approx(5.0)
+
+
+def test_calculate_angle():
+    assert calculate_angle((0, 1), (1, 0)) == pytest.approx(90.0)
+    assert calculate_angle((1, 0), (0, 1)) == pytest.approx(90.0)
+    assert calculate_angle((1, 0), (1, 1)) == pytest.approx(45.0)
+
+
+def test_calc_ellipse_area():
+    assert isclose(calc_ellipse_area(3, 5), 47.1238, rel_tol=1e-4)
+    assert isclose(calc_ellipse_area(10, 10), 314.1592, rel_tol=1e-4)
+
+
+def test_calc_ellipse_circumference():
+    assert isclose(calc_ellipse_circumference(4, 4), 12.5664, rel_tol=1e-4)
+    assert isclose(calc_ellipse_circumference(10, 5), 24.221, rel_tol=1e-4)
+
+
+def test_calc_polygon_area():
+    assert calc_polygon_area([(0, 0), (0, 2), (2, 2), (2, 0)]) == pytest.approx(4.0)
+    assert calc_polygon_area([(0, 0), (0, 9), (6, 0)]) == pytest.approx(27.0)
+
+
+def test_calc_polygon_perimeter():
+    assert calc_polygon_perimeter([(0, 0), (0, 2), (2, 2), (2, 0)]) == pytest.approx(8.0)
+    assert calc_polygon_perimeter([(0, 0), (0, 4), (3, 0)]) == pytest.approx(12.0)
+
+
+def test_inner1d():
+    a = np.array((1, 2, 3))
+    b = np.array((4, 5, 6))
+    assert inner1d(a, b) == 32
+
+    a = np.arange(9).reshape(3, 3)
+    b = np.arange(9).reshape(3, 3)
+    np.testing.assert_array_equal(inner1d(a, b), np.array([5, 50, 149]))
diff --git a/tests/test_session.py b/tests/test_session.py
index 170909699..5b1582c94 100644
--- a/tests/test_session.py
+++ b/tests/test_session.py
@@ -1,6 +1,7 @@
 import json
 import os
 import sys
+from pathlib import Path
 from unittest.mock import call, mock_open
 
 import pytest
@@ -20,10 +21,11 @@ def test_set_and_get_config():
 def test_write_config_file(mocker):
     mock_file = mock_open()
     mocker.patch("builtins.open", mock_file)
+    mocker.patch("pathlib.Path.mkdir")
     mock_json_dump = mocker.patch("json.dump")
     session._config = {"debug": True, "language": "en", "file_logging": 1}
     session.WriteConfigFile()
-    mock_file.assert_called_once_with(CONFIG_PATH, "w")
+    mock_file.assert_called_once_with(Path(CONFIG_PATH), "w")
     expected_data = {"debug": True, "language": "en", "file_logging": 1}
     mock_json_dump.assert_called_once_with(expected_data, mock_file(), sort_keys=True, indent=4)
 
@@ -67,20 +69,23 @@ def test_read_state(mocker):
 def test_create_state(mocker):
     mock_state_json = mock_open()
     mocker.patch("builtins.open", mock_state_json)
+    mocker.patch("pathlib.Path.mkdir")
     mock_json_dump = mocker.patch("json.dump")
     session.CreateState()
-    mock_state_json.assert_called_once_with(STATE_PATH, "w")
+    mock_state_json.assert_called_once_with(Path(STATE_PATH), "w")
     mock_json_dump.assert_called_once_with({}, mock_state_json(), sort_keys=True, indent=4)
 
 
 def test_set_and_get_state(mocker):
     mock_file = mock_open()
     mocker.patch("builtins.open", mock_file)
+    mocker.patch("pathlib.Path.mkdir")
     mock_json = mocker.patch("json.dump")
     session.SetState("test_key", "test_value")
-    mock_file.assert_called_once_with(STATE_PATH, "w")
-    mock_json.assert_called_once_with(session._state, mock_file(), sort_keys=True, indent=4)
-    assert session.GetState("test_key") == "test_value"
+    mock_file.assert_called_once_with(Path(STATE_PATH), "w")
+    mock_json.assert_called_once_with(
+        {"test_key": "test_value"}, mock_file(), sort_keys=True, indent=4
+    )
 
 
 def test_delete_state_file(mocker):


Reviews:
user: login: tfmoraes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T14:18:51Z
user: login: tfmoraes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T14:19:22Z
user: login: tfmoraes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T14:21:43Z
user: login: n33lp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T18:12:18Z
user: login: n33lp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T18:12:30Z
user: login: n33lp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T18:12:39Z
user: login: tfmoraes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T18:55:39Z
user: login: tfmoraes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T18:56:10Z
user: login: tfmoraes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T18:57:18Z
user: login: n33lp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T19:12:04Z
user: login: n33lp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T19:29:31Z
user: login: n33lp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T19:29:41Z

Review comments:
diff_hunk: @@ -0,0 +1,65 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-20.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - name: Cache dependencies (uv cache + virtualenv)
+        uses: actions/cache@v3
+        with:
+          path: venv
+          key: uv-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}
path: .github/workflows/cache.yml
user: login: tfmoraes
body: Please, use `uv.lock` instead of `requirements.txt`
created_at: 2025-04-01T14:18:51Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,65 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-20.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - name: Cache dependencies (uv cache + virtualenv)
+        uses: actions/cache@v3
+        with:
+          path: venv
+          key: uv-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}
+          restore-keys: |
+            uv-${{ runner.os }}-
+
+      - uses: astral-sh/setup-uv@v5
+        with:
+          python-version: 3.12
+          enable-cache: false
+
+      - name: Install dependencies with uv
+        run: |
+          uv venv venv
+          source venv/bin/activate
+          uv pip install -r requirements.txt
path: .github/workflows/cache.yml
user: login: tfmoraes
body: use `uv sync` to install dependencies.
created_at: 2025-04-01T14:19:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,65 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-20.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - name: Cache dependencies (uv cache + virtualenv)
+        uses: actions/cache@v3
path: .github/workflows/cache.yml
user: login: tfmoraes
body: See this for uv with cache https://docs.astral.sh/uv/guides/integration/github/#caching
created_at: 2025-04-01T14:21:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,65 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-20.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - name: Cache dependencies (uv cache + virtualenv)
+        uses: actions/cache@v3
+        with:
+          path: venv
+          key: uv-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}
path: .github/workflows/cache.yml
user: login: n33lp
body: done
created_at: 2025-04-01T18:12:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,65 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-20.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - name: Cache dependencies (uv cache + virtualenv)
+        uses: actions/cache@v3
+        with:
+          path: venv
+          key: uv-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}
+          restore-keys: |
+            uv-${{ runner.os }}-
+
+      - uses: astral-sh/setup-uv@v5
+        with:
+          python-version: 3.12
+          enable-cache: false
+
+      - name: Install dependencies with uv
+        run: |
+          uv venv venv
+          source venv/bin/activate
+          uv pip install -r requirements.txt
path: .github/workflows/cache.yml
user: login: n33lp
body: done
created_at: 2025-04-01T18:12:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,65 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-20.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - name: Cache dependencies (uv cache + virtualenv)
+        uses: actions/cache@v3
path: .github/workflows/cache.yml
user: login: n33lp
body: done
created_at: 2025-04-01T18:12:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,63 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - uses: astral-sh/setup-uv@v5
+        with:
+          python-version: 3.12
+          enable-cache: true
+          cache-dependency-glob: "uv.lock"
+
+      - name: Set up virtualenv
+        run: uv venv venv
path: .github/workflows/cache.yml
user: login: tfmoraes
body: This step is not necessary. just use `uv sync`
created_at: 2025-04-01T18:55:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,63 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - uses: astral-sh/setup-uv@v5
+        with:
+          python-version: 3.12
+          enable-cache: true
+          cache-dependency-glob: "uv.lock"
+
+      - name: Set up virtualenv
+        run: uv venv venv
+
+      - name: Install dependencies using uv.lock
+        run: |
+          source venv/bin/activate
path: .github/workflows/cache.yml
user: login: tfmoraes
body: it's not necessary to activate the virtualenv to use `uv sync`.
created_at: 2025-04-01T18:56:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,74 @@
+name: tests
+
+on:
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  run-tests:
+    runs-on: ubuntu-22.04 #test
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0  
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+
+      - uses: astral-sh/setup-uv@v5
+        with:
+          python-version: 3.12
+          enable-cache: true
+          cache-dependency-glob: "uv.lock"
+
+      - name: Set up virtualenv
+        run: uv venv venv
+
+      - name: Install dependencies using uv.lock
+        run: |
+          source venv/bin/activate
+          uv sync
+
+      - name: Run tests with xvfb
+        run: xvfb-run -a bash -c "source venv/bin/activate && pytest"
path: .github/workflows/tests.yml
user: login: tfmoraes
body: xvfb is needed because of wxpython? Other thing, you don't need to activate the virtualenv, just run `uv run pytest`.
created_at: 2025-04-01T18:57:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,74 @@
+name: tests
+
+on:
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  run-tests:
+    runs-on: ubuntu-22.04 #test
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0  
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+
+      - uses: astral-sh/setup-uv@v5
+        with:
+          python-version: 3.12
+          enable-cache: true
+          cache-dependency-glob: "uv.lock"
+
+      - name: Set up virtualenv
+        run: uv venv venv
+
+      - name: Install dependencies using uv.lock
+        run: |
+          source venv/bin/activate
+          uv sync
+
+      - name: Run tests with xvfb
+        run: xvfb-run -a bash -c "source venv/bin/activate && pytest"
path: .github/workflows/tests.yml
user: login: n33lp
body: xvfb is there for GUI testing. Without it, some tests would fail on the GitHub runners. Thanks, got rid of the virtualenv
created_at: 2025-04-01T19:12:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,63 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - uses: astral-sh/setup-uv@v5
+        with:
+          python-version: 3.12
+          enable-cache: true
+          cache-dependency-glob: "uv.lock"
+
+      - name: Set up virtualenv
+        run: uv venv venv
+
+      - name: Install dependencies using uv.lock
+        run: |
+          source venv/bin/activate
path: .github/workflows/cache.yml
user: login: n33lp
body: removed!
created_at: 2025-04-01T19:29:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,63 @@
+name: build-cache
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build-pip-cache:
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.12
+
+      - name: Install Ubuntu dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y \
+                  freeglut3-dev \
+                  libcurl4-openssl-dev \
+                  libexpat1-dev \
+                  libgl1-mesa-dev \
+                  libglu1-mesa-dev \
+                  libgtk-3-dev \
+                  libjpeg-dev \
+                  libnotify-dev \
+                  libsdl2-dev \
+                  libsm-dev \
+                  libtiff-dev \
+                  libwebkit2gtk-4.0-dev \
+                  libxtst-dev \
+                  libunwind-dev \
+                  libgstreamer1.0-dev \
+                  libgstreamer-plugins-base1.0-dev \
+                  libpng-dev \
+                  libxrandr-dev \
+                  libxinerama-dev \
+                  libxcursor-dev \
+                  libxcomposite-dev \
+                  libfreetype6-dev \
+                  libxi-dev \
+
+      - uses: astral-sh/setup-uv@v5
+        with:
+          python-version: 3.12
+          enable-cache: true
+          cache-dependency-glob: "uv.lock"
+
+      - name: Set up virtualenv
+        run: uv venv venv
path: .github/workflows/cache.yml
user: login: n33lp
body: Removed!
created_at: 2025-04-01T19:29:41Z
author_association: CONTRIBUTOR

PR comments:
user: login: tfmoraes
created_at: 2025-03-21T21:16:16Z
body: Hi @n33lp. We created a folder to add the tests see `invesalius3/tests`
author_association: MEMBER
user: login: n33lp
created_at: 2025-03-21T21:27:11Z
body: Hi @tfmoraes, I've moved my tests into that folder. Any thoughts on the workflow file I added?
author_association: CONTRIBUTOR
user: login: tfmoraes
created_at: 2025-03-27T13:44:13Z
body: Hi @n33lp. Thanks for the PR! Please, use UV to instead of pip to install the requirements. Also try to use cache to avoid this problem with compilation time. See this https://github.com/astral-sh/setup-uv?tab=readme-ov-file#enable-caching
author_association: MEMBER
user: login: n33lp
created_at: 2025-03-28T07:01:42Z
body: Hi @tfmoraes, I finally got it to work. These are the changes:

build-cache.yml (runs on push to master)
- Checks out the repository
- Sets up Python 3.12
- Installs Ubuntu system dependencies needed for packages like wxPython and vtk
- Caches the venv directory using a key based on the OS and hash of requirements.txt
- Uses uv to:
  - Create a virtual environment (uv venv venv)
  - Activate it
  - Install dependencies with uv pip install -r requirements.txt
- The venv is saved as a cache if requirements.txt has changed

tests.yml (runs on pull_request to master)
- Checks out the PR branch
- Sets up Python 3.12
- Installs the same Ubuntu system dependencies
- Attempts to restore the cached venv using the same cache key
- If the venv folder is not found:
  - Creates the virtual environment with uv venv venv
  - Activates it and installs dependencies
- If the venv folder is found:
  - Activates it without reinstalling
- Runs tests using xvfb-run so GUI-based libraries like wxPython work in CI

How they work together
- build-cache.yml prepares and caches the venv
- tests.yml restores the venv to avoid reinstalling heavy dependencies
- Caching is based on the hash of requirements.txt
- If requirements.txt doesn't change, the same cache is reused
- Cache key: uv-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}


I'll revisit those failed tests, if everything else looks good.
author_association: CONTRIBUTOR
user: login: n33lp
created_at: 2025-04-01T18:13:11Z
body: @tfmoraes I've made the edits you've asked. Thanks for the feedback!
author_association: CONTRIBUTOR
user: login: tfmoraes
created_at: 2025-04-01T19:00:15Z
body: Hi @n33lp. Why did you created 2 workflow files? I think you can use just 1. Other thing ... The modifications in `session.py` and `CMakeLists.txt` and needed for this PR. I think you can create other one.
author_association: MEMBER
user: login: n33lp
created_at: 2025-04-01T19:44:31Z
body: > Hi @n33lp. Why did you created 2 workflow files? I think you can use just 1. Other thing ... The modifications in `session.py` and `CMakeLists.txt` and needed for this PR. I think you can create other one.

Hi @tfmoraes couple things:

If I move the `CMakeLists.txt` changes to a new PR, `uv sync` will fail because the top-level `CMakeLists.txt` tried to include a subdirectory(plugins) that did not exist or was missing a CMakeLists.txt file.

Originally, I made 2 different yml files because I couldn't access the cache across different PRs—I could only access the cache built and stored on the master branch. It's still structured that way now: One workflow is dedicated to building and updating the cache on pushes to master, while the other handles test runs on PRs and leverages that cache. This setup ensures that the test workflow always has access to a consistent, centralized cache without having to rebuild it for every pull request. I also wanted to prevent premature optimization. That said, I'm open to feedback if there's a cleaner or more efficient way to approach this.

If I move the code for session/test_session to a new PR, the test workflow will fail due to how the runners are set up. I can move the files, but that PR would need to be merged first, and this PR would then need to be updated accordingly. Would you still like me to split it up?

author_association: CONTRIBUTOR
user: login: tfmoraes
created_at: 2025-04-01T21:06:17Z
body: I think it's OK. I'll merge it. Thanks!
author_association: MEMBER