diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fee7884 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +# Cancel superseded runs on the same ref (e.g. rapid pushes to a PR). +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: Lint (syntax / undefined names) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install ruff + run: pip install ruff + # Only fail on real errors: syntax errors and undefined names. + # This is a fast safety net, not a style gate. + - name: Check for fatal errors + run: ruff check --select E9,F63,F7,F82 src tests + + test: + name: Tests (py${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.11"] + env: + # Run Qt without a display. + QT_QPA_PLATFORM: offscreen + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: pyproject.toml + + # System libraries needed by PyQt5 / napari / VTK in a headless runner. + - name: Install Qt system dependencies + uses: tlambert03/setup-qt-libs@v1 + + - name: Install package and test dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[test]" + + - name: Run test suite + run: pytest tests/ -v diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b55f36..271e00f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,6 +63,22 @@ If you encounter issues, please open an issue or reach out for help. - Add tests for new features and bug fixes when possible. - If adding new modules, consider including a corresponding test file. +Install the test dependencies and run the suite: + +```bash +pip install -e ".[test]" + +# GUI tests need a Qt platform; use the offscreen backend when headless. +QT_QPA_PLATFORM=offscreen pytest tests/ +``` + +Markers: +- `gui` — needs a Qt application (deselect with `-m "not gui"`) +- `integration` — needs real data paths + +The full suite runs automatically on every push and pull request via GitHub +Actions (`.github/workflows/ci.yml`). + ## Reporting Bugs If you find a bug: 1. Search [existing issues](https://github.com/baradlab/surface-morphometrics-gui/issues) to see if it has already been reported. diff --git a/pyproject.toml b/pyproject.toml index aeae522..a3420a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,15 @@ dependencies = [ "libigl<2.5.0", ] +[project.optional-dependencies] +# Test/dev tooling. PyQt5 is included so the suite has a Qt binding outside the +# conda `morphometrics` env (e.g. in CI / a plain virtualenv). +test = [ + "pytest", + "pytest-qt", + "PyQt5", +] + [project.scripts] surface-morphometrics-gui = "surface_morphometrics_gui.main:main" diff --git a/tests/test_known_bugs.py b/tests/test_known_bugs.py index 6ecbb29..c085382 100644 --- a/tests/test_known_bugs.py +++ b/tests/test_known_bugs.py @@ -181,12 +181,14 @@ def test_no_clear_during_programmatic_clear(self, qapp): em.data_dir.value = "/user/data" em.cores_input.setValue(8) - # Simulate programmatic clear (index goes to -1) + # Simulate programmatic clear (index goes to -1); this fires + # _on_experiment_selected via the currentIndexChanged signal. em.experiment_name.setCurrentIndex(-1) - # _on_experiment_selected fires via signal - # After fix, user values should be preserved during programmatic clears - # (The fix should guard against clearing during non-user-initiated changes) + # The fix guards on currentIndex() >= 0, so a negative index + # (non-user-initiated change) must not wipe the user's values. + assert str(em.data_dir.value) == "/user/data" + assert em.cores_input.value() == 8 class TestBug10_TimeSleep: