diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 00000000..b5dba491 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,58 @@ +name: Test all platforms + +on: [ pull_request, workflow_dispatch ] + +jobs: + test: + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest, macos-13, macos-latest, windows-latest ] + python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Linux packages for Qt5/Qt6 support and start Xvfb + if: runner.os == 'Linux' + uses: pyvista/setup-headless-display-action@v3 + with: + qt: true + + - name: Linux OpenCL support + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y pocl-opencl-icd ocl-icd-opencl-dev gcc clinfo + clinfo + + - name : Windows OpenCL support + if: runner.os == 'Windows' + run: | + choco install opencl-intel-cpu-runtime --no-progress --yes + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install setuptools>=40.8.0 wheel pytest + python -m pip install . + python -m pip freeze --all + + - name: Validate OpenCL + if: matrix.os != 'macos-latest' # no OpenCL device in macOS runners since macos-14 + run: | + python -c "from pytissueoptics.rayscattering.opencl import OPENCL_OK; assert OPENCL_OK" + + - name: Run tests + env: + PTO_CI_MODE: 1 + run: | + python -m pytest -v diff --git a/pytissueoptics/rayscattering/opencl/config/CLConfig.py b/pytissueoptics/rayscattering/opencl/config/CLConfig.py index d4b48e1c..4e3e50a0 100644 --- a/pytissueoptics/rayscattering/opencl/config/CLConfig.py +++ b/pytissueoptics/rayscattering/opencl/config/CLConfig.py @@ -178,6 +178,12 @@ def _load(self): with open(OPENCL_CONFIG_PATH, "r") as f: self._config = json.load(f) + if os.getenv("PTO_CI_MODE", "0") == "1": + warnings.warn("Using default OpenCL configuration for CI mode.") + self.DEVICE_INDEX = 0 + self.N_WORK_UNITS = 128 + self.MAX_MEMORY_MB = 1024 + def _assertExists(self): if not os.path.exists(OPENCL_CONFIG_PATH): warnings.warn("No OpenCL config file found. Creating a new one.") diff --git a/pytissueoptics/rayscattering/tests/opencl/config/testCLConfig.py b/pytissueoptics/rayscattering/tests/opencl/config/testCLConfig.py index e9c3a6ff..e2999338 100644 --- a/pytissueoptics/rayscattering/tests/opencl/config/testCLConfig.py +++ b/pytissueoptics/rayscattering/tests/opencl/config/testCLConfig.py @@ -1,6 +1,7 @@ import os import tempfile import unittest +from unittest.mock import patch from pytissueoptics.rayscattering.opencl import OPENCL_OK from pytissueoptics.rayscattering.opencl.config import CLConfig as clc @@ -25,11 +26,17 @@ def testGivenNoConfigFile_shouldWarnAndCreateANewOne(self): self.assertTrue(os.path.exists(clc.OPENCL_CONFIG_PATH)) @tempConfigPath - def testGivenNewConfigFile_shouldHaveDefaultValues(self): + def testGivenNewConfigFile_shouldHaveDefaultsFromEnvironment(self): with self.assertWarns(UserWarning): config = clc.CLConfig() - self.assertEqual(None, config.N_WORK_UNITS) - self.assertEqual(None, config.MAX_MEMORY_MB) + if os.getenv("PTO_CI_MODE", "0") == "1": + self.assertEqual(0, config.DEVICE_INDEX) + self.assertEqual(128, config.N_WORK_UNITS) + self.assertEqual(1024, config.MAX_MEMORY_MB) + else: + self.assertEqual(None, config.DEVICE_INDEX) + self.assertEqual(None, config.N_WORK_UNITS) + self.assertEqual(None, config.MAX_MEMORY_MB) self.assertEqual(1000, config.IPP_TEST_N_PHOTONS) self.assertEqual(0.20, config.BATCH_LOAD_FACTOR) @@ -46,7 +53,8 @@ def testGivenMaxMemoryNotSet_whenValidate_shouldWarnAndSetMaxMemory(self): with open(clc.OPENCL_CONFIG_PATH, "w") as f: f.write('{"DEVICE_INDEX": 0, "N_WORK_UNITS": 100, "MAX_MEMORY_MB": null, ' '"IPP_TEST_N_PHOTONS": 1000, "BATCH_LOAD_FACTOR": 0.2}') - config = clc.CLConfig() + with patch("os.getenv", return_value=None): + config = clc.CLConfig() with self.assertWarns(UserWarning): config.validate() self.assertIsNotNone(config.MAX_MEMORY_MB) @@ -68,8 +76,10 @@ def testGivenFileWithAParameterBelowOrEqualToZero_whenValidate_shouldResetDefaul with open(clc.OPENCL_CONFIG_PATH, "w") as f: f.write('{"DEVICE_INDEX": 0, "N_WORK_UNITS": 100, "MAX_MEMORY_MB": 0, ' '"IPP_TEST_N_PHOTONS": 1000, "BATCH_LOAD_FACTOR": 0.2}') - config = clc.CLConfig() - with self.assertRaises(ValueError): - config.validate() - config = clc.CLConfig() - self.assertIsNone(config.MAX_MEMORY_MB) + + with patch("os.getenv", return_value=None): + config = clc.CLConfig() + with self.assertRaises(ValueError): + config.validate() + config = clc.CLConfig() + self.assertIsNone(config.MAX_MEMORY_MB)