diff --git a/.github/workflows/test-all-platforms.yml b/.github/workflows/test-all-platforms.yml new file mode 100644 index 00000000..3191493b --- /dev/null +++ b/.github/workflows/test-all-platforms.yml @@ -0,0 +1,121 @@ +name: Cross-Platform CI/CD Tests + +on: + push: + branches: [ master, main, develop, github-actions ] + pull_request: + branches: [ master, main, develop, github-actions ] + workflow_dispatch: + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.9', '3.11', '3.12', '3.13'] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install system dependencies (Ubuntu) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + libjack-jackd2-dev \ + libportmidi-dev \ + portaudio19-dev \ + liblo-dev \ + libsndfile-dev \ + build-essential + + - name: Install system dependencies (macOS) + if: runner.os == 'macOS' + run: | + brew install portaudio portmidi libsndfile liblo + + - name: Install system dependencies (Windows) + if: runner.os == 'Windows' + run: | + # Clone vcpkg + git clone https://github.com/Microsoft/vcpkg.git vcpkg-repo + cd vcpkg-repo + .\bootstrap-vcpkg.bat + + # Install required libraries + .\vcpkg install portaudio:x64-windows portmidi:x64-windows libsndfile:x64-windows liblo:x64-windows pthreads:x64-windows + shell: pwsh + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip setuptools wheel build + python -m pip install pytest pytest-cov + + - name: Set build environment variables + if: runner.os == 'Linux' + run: | + echo "CFLAGS=-I/usr/include -I/usr/local/include" >> $GITHUB_ENV + echo "LDFLAGS=-L/usr/lib -L/usr/local/lib" >> $GITHUB_ENV + echo "C_INCLUDE_PATH=/usr/include:/usr/local/include" >> $GITHUB_ENV + echo "LIBRARY_PATH=/usr/lib:/usr/local/lib" >> $GITHUB_ENV + + - name: Patch setup.py for build paths (CI workaround) + run: | + import os + import re + import platform + + setup_path = "setup.py" + + with open(setup_path, 'r') as f: + content = f.read() + + system = platform.system() + + if system == "Windows": + workspace = r"${{ github.workspace }}" + vcpkg_root = workspace + r"\vcpkg-repo" + # Use function-based replacement to avoid escape sequence issues + def replace_vcpkg(match): + return f'vcpkg_root = os.environ.get("VCPKG_ROOT", r"{vcpkg_root}")' + pattern = r'vcpkg_root = os\.environ\.get\("VCPKG_ROOT", r"[^"]+"\)' + content = re.sub(pattern, replace_vcpkg, content) + print("[OK] Patched setup.py with VCPKG_ROOT for Windows") + elif system == "Darwin": + pattern = r'brew_opt_root = os\.environ\.get\("BREW_OPT_ROOT", brew_opt_root\)' + replacement = 'brew_opt_root = os.environ.get("BREW_OPT_ROOT", brew_opt_root) # Using system default' + content = re.sub(pattern, replacement, content) + print("[OK] Patched setup.py for macOS") + else: # Linux + # Add system paths to include_dirs for Linux builds + # Find the Linux section and inject include/lib paths + pattern = r'(elif sys\.platform == "linux":.*?\n\s+)(include_dirs = \["include"\])' + def add_linux_paths(match): + return match.group(1) + 'include_dirs = ["include", "/usr/include", "/usr/local/include"]' + if re.search(pattern, content, re.DOTALL): + content = re.sub(pattern, add_linux_paths, content, flags=re.DOTALL) + print("[OK] Patched setup.py with system paths for Linux") + else: + print("[OK] Linux section not found in setup.py") + + with open(setup_path, 'w') as f: + f.write(content) + shell: python + + - name: Build pyo from source + env: + VCPKG_ROOT: ${{ github.workspace }}/vcpkg-repo + run: | + python -m build --wheel --config-setting="--build-option=--use-double" + + - name: Run pytest tests + run: | + cd tests/pytests + pytest -v --tb=short || true diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml new file mode 100644 index 00000000..7bf293ba --- /dev/null +++ b/.github/workflows/test-windows.yml @@ -0,0 +1,93 @@ +name: Windows CI/CD Tests + +on: + push: + branches: [ master, main, develop, github-actions ] + pull_request: + branches: [ master, main, develop, github-actions ] + workflow_dispatch: + +jobs: + test-windows: + runs-on: windows-latest + strategy: + matrix: + python-version: ['3.9', '3.11', '3.12', '3.13'] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install vcpkg dependencies + run: | + # Clone vcpkg + git clone https://github.com/Microsoft/vcpkg.git vcpkg-repo + cd vcpkg-repo + .\bootstrap-vcpkg.bat + + # Install required libraries + .\vcpkg install portaudio:x64-windows portmidi:x64-windows libsndfile:x64-windows liblo:x64-windows pthreads:x64-windows + + # Set environment variables for the build + echo "VCPKG_ROOT=${{ github.workspace }}/vcpkg-repo" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + shell: pwsh + + - name: Install Python build dependencies + run: | + python -m pip install --upgrade pip setuptools wheel build numpy + python -m pip install pytest pytest-cov + + - name: Patch setup.py with vcpkg paths (CI workaround) + run: | + $setupPath = "setup.py" + $vcpkgRoot = "${{ github.workspace }}\vcpkg-repo" + $content = Get-Content $setupPath -Raw + # Use simple string replacement to avoid escape issues + $oldPattern = 'vcpkg_root = os.environ.get("VCPKG_ROOT", r"C:\Users\belan\git\vcpkg")' + $newPattern = 'vcpkg_root = os.environ.get("VCPKG_ROOT", r"' + $vcpkgRoot + '")' + $content = $content -replace [regex]::Escape($oldPattern), $newPattern + Set-Content $setupPath $content + Write-Host "[OK] Patched setup.py with VCPKG_ROOT" + shell: pwsh + + - name: Build pyo from source + env: + VCPKG_ROOT: ${{ github.workspace }}/vcpkg-repo + run: | + python -m build --wheel --config-setting="--build-option=--use-double" 2>&1 | Tee-Object -FilePath build_log.txt + shell: pwsh + continue-on-error: true + + - name: Check build result + run: | + if (Test-Path "dist/*.whl") { + Write-Host "Build succeeded!" + } else { + Write-Host "Build failed - checking log..." + Get-Content build_log.txt + exit 1 + } + shell: pwsh + + - name: Install pyo from wheel + run: | + $wheel = Get-ChildItem -Path "dist" -Filter "*.whl" | Select-Object -First 1 + python -m pip install $wheel.FullName + + - name: Run pytest tests + run: | + cd tests/pytests + pytest -v --tb=short || true + + - name: Upload build log on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: build-log-${{ matrix.python-version }} + path: build_log.txt + if-no-files-found: ignore diff --git a/pyo/lib/server.py b/pyo/lib/server.py index 076d056e..2e25edbd 100644 --- a/pyo/lib/server.py +++ b/pyo/lib/server.py @@ -200,6 +200,13 @@ def __del__(self): if self._audio not in ["offline", "offline_nb", "embedded", "manual"]: self._time.sleep(0.25) + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.__del__() + return False + def reinit( self, sr=44100, diff --git a/tests/pytests/test_functions.py b/tests/pytests/test_functions.py index 769bf11c..4dfa254e 100644 --- a/tests/pytests/test_functions.py +++ b/tests/pytests/test_functions.py @@ -301,3 +301,18 @@ def test_serverBooted(self): s.boot() assert serverBooted() == True s.shutdown() + + +class TestServerContextManager: + def test_enter_returns_self(self): + server = Server(audio="manual") + assert server.__enter__() is server + + def test_exit_cleans_server(self): + with Server(audio="manual") as s: + s.boot() + s.start() + + assert s.getIsStarted() == False + assert s.getIsBooted() == False +