diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index 324115f..a8800fc 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -4,6 +4,7 @@ name: PR Tests # - Draft PRs: Fast tests only (no coverage) for rapid iteration # - Ready for Review: Full tests with coverage for quality assurance # - All subsequent commits: Continue with full coverage +# - Pyomo tests run separately and don't block PRs on: pull_request: @@ -11,7 +12,8 @@ on: types: [ opened, synchronize, reopened, ready_for_review, converted_to_draft ] jobs: - test: + test-scipy: + name: SciPy Tests runs-on: ubuntu-latest steps: @@ -50,11 +52,11 @@ jobs: - name: Run tests run: | if [ "${{ steps.mode.outputs.mode }}" == "fast" ]; then - echo "⚡ Draft PR - fast tests only (no notebook tests)" - pytest tests/ -n auto -v -m "not notebook" + echo "Draft PR: Skipping notebook and pyomo tests for fast feedback" + pytest tests/ -n auto -v -m "not notebook and not pyomo" else - echo "🔍 Full PR - running tests with coverage report" - pytest tests/ -n auto -v -m "not notebook" --cov-report=xml:coverage.xml + echo "Ready PR: Running full scipy test suite (excluding notebook and pyomo)" + pytest tests/ -n auto -v -m "not notebook and not pyomo" --cov=lyopronto --cov-report=xml:coverage.xml fi - name: Upload coverage @@ -66,3 +68,51 @@ jobs: name: pr-coverage fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} + + test-pyomo: + name: Pyomo Tests (Optional) + if: ${{ github.event.pull_request.draft == false }} + runs-on: ubuntu-latest + continue-on-error: true # Pyomo tests are brittle, don't block PRs + + steps: + - uses: actions/checkout@v4 + + - name: Read CI version config + id: versions + uses: mikefarah/yq@v4.44.1 + with: + cmd: yq eval '.python-version' .github/ci-config/ci-versions.yml + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ steps.versions.outputs.result }} + cache: 'pip' + cache-dependency-path: | + pyproject.toml + + - name: Install dependencies with optimization stack + run: | + python -m pip install --upgrade pip setuptools wheel + pip install . + pip install .[dev] + pip install pyomo idaes-pse + pip install -e . --no-build-isolation + + - name: Install IPOPT solver via IDAES + run: | + echo "Installing IPOPT solver via IDAES extensions" + idaes get-extensions --extra petsc + + - name: Run Pyomo tests + run: | + echo "Running Pyomo test suite (continue-on-error enabled)" + # Exit code 5 = no tests collected (pyomo tests added in later PRs) + pytest tests/ -n auto -v -m "pyomo" --cov=lyopronto --cov-report=term-missing || { rc=$?; [ $rc -eq 5 ] && echo "No pyomo-marked tests found yet (expected until PR #8)" && exit 0; exit $rc; } + + - name: Pyomo Test Summary + if: always() + run: | + echo "Pyomo tests completed" + echo "Failures here don't block PR merge" diff --git a/.github/workflows/slow-tests.yml b/.github/workflows/slow-tests.yml index e3f7d7e..20df2e0 100644 --- a/.github/workflows/slow-tests.yml +++ b/.github/workflows/slow-tests.yml @@ -17,6 +17,14 @@ on: options: - 'true' - 'false' + include_pyomo: + description: 'Include Pyomo tests (requires IPOPT)' + required: false + default: 'true' + type: choice + options: + - 'true' + - 'false' jobs: slow-tests: @@ -43,18 +51,39 @@ jobs: python -m pip install --upgrade pip setuptools wheel pip install . pip install .[dev] + if [ "${{ inputs.include_pyomo }}" == "true" ]; then + pip install pyomo idaes-pse + fi pip install -e . --no-build-isolation + - name: Install IPOPT solver via IDAES (if Pyomo enabled) + if: inputs.include_pyomo == 'true' + run: | + echo "Installing IPOPT solver via IDAES extensions" + idaes get-extensions --extra petsc + - name: Run slow tests + env: + RUN_SLOW_TESTS: "1" run: | + # Helper: allow exit code 5 (no tests collected) to pass gracefully + run_pytest() { "$@" || { rc=$?; [ $rc -eq 5 ] && echo "No matching tests found (exit 5 is OK)" && return 0; return $rc; }; } if [ "${{ inputs.run_all }}" == "true" ]; then - echo "🔍 Running ALL tests (including slow optimization tests)" - echo "⏱️ This may take 30-40 minutes on CI" - pytest tests/ -n auto -v --cov=lyopronto --cov-report=xml --cov-report=term-missing + echo "Running ALL tests (including slow optimization tests)" + echo "This may take 30-40 minutes on CI" + if [ "${{ inputs.include_pyomo }}" == "true" ]; then + run_pytest pytest tests/ -n auto -v --cov=lyopronto --cov-report=xml --cov-report=term-missing + else + run_pytest pytest tests/ -n auto -v -m "not pyomo" --cov=lyopronto --cov-report=xml --cov-report=term-missing + fi else - echo "🐌 Running ONLY slow tests (marked with @pytest.mark.slow)" - echo "⏱️ This focuses on optimization tests that take minutes" - pytest tests/ -n auto -v -m "slow" --cov=lyopronto --cov-report=xml --cov-report=term-missing + echo "Running ONLY slow tests (marked with @pytest.mark.slow)" + echo "This focuses on optimization tests that take minutes" + if [ "${{ inputs.include_pyomo }}" == "true" ]; then + run_pytest pytest tests/ -n auto -v -m "slow" --cov=lyopronto --cov-report=xml --cov-report=term-missing + else + run_pytest pytest tests/ -n auto -v -m "slow and not pyomo" --cov=lyopronto --cov-report=xml --cov-report=term-missing + fi fi - name: Upload coverage @@ -70,8 +99,8 @@ jobs: if: always() run: | if [ "${{ inputs.run_all }}" == "true" ]; then - echo "✅ Complete test suite finished" + echo "Complete test suite finished" else - echo "🐌 Slow tests completed" + echo "Slow tests completed" fi - echo "📊 Coverage uploaded to Codecov" + echo "Coverage uploaded to Codecov" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d7904b2..e3be451 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,14 +1,15 @@ name: Main Branch Tests # Full tests with coverage for main branch -# (PRs are handled by pr-tests.yml) +# Runs both scipy tests and Pyomo tests in separate jobs on: push: - branches: [ main, dev-pyomo ] + branches: [ main ] jobs: - test: + test-scipy: + name: SciPy Tests runs-on: ubuntu-latest steps: @@ -33,23 +34,77 @@ jobs: pip install .[dev] pip install -e . --no-build-isolation - - name: Run ALL tests with pytest and coverage (including slow tests) + - name: Run SciPy tests (excluding Pyomo tests) run: | - echo "🔍 Running complete test suite including slow tests" - echo "⏱️ This may take 30-40 minutes on CI (includes optimization tests)" - pytest tests/ -n auto -v --cov=lyopronto --cov-report=xml --cov-report=term-missing + echo "Running SciPy test suite (excluding Pyomo tests)" + pytest tests/ -n auto -v -m "not pyomo" --cov=lyopronto --cov-report=xml --cov-report=term-missing - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: file: ./coverage.xml - flags: unittests - name: codecov-umbrella + flags: scipy-tests + name: scipy-coverage fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} - name: Coverage Summary if: always() run: | - echo "✅ Full coverage tests completed for main branch" - echo "📊 Coverage metrics updated in Codecov" + echo "SciPy tests completed for main branch" + echo "Coverage metrics updated in Codecov" + + test-pyomo: + name: Pyomo Tests + runs-on: ubuntu-latest + continue-on-error: true # Pyomo tests are brittle, don't block merges + + steps: + - uses: actions/checkout@v4 + - name: Read CI version config + id: versions + uses: mikefarah/yq@v4.44.1 + with: + cmd: yq eval '.python-version' .github/ci-config/ci-versions.yml + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ steps.versions.outputs.result }} + cache: 'pip' + cache-dependency-path: | + pyproject.toml + + - name: Install dependencies with optimization stack + run: | + python -m pip install --upgrade pip setuptools wheel + pip install . + pip install .[dev] + pip install pyomo idaes-pse + pip install -e . --no-build-isolation + + - name: Install IPOPT solver via IDAES + run: | + echo "Installing IPOPT solver via IDAES extensions" + idaes get-extensions --extra petsc + + - name: Run Pyomo tests + run: | + echo "Running Pyomo test suite" + echo "These tests require IPOPT solver and may be slower" + # Exit code 5 = no tests collected (pyomo tests added in later PRs) + pytest tests/ -n auto -v -m "pyomo" --cov=lyopronto --cov-report=xml --cov-report=term-missing || { rc=$?; [ $rc -eq 5 ] && echo "No pyomo-marked tests found yet (expected until PR #8)" && exit 0; exit $rc; } + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.xml + flags: pyomo-tests + name: pyomo-coverage + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Pyomo Test Summary + if: always() + run: | + echo "Pyomo tests completed (continue-on-error enabled)" + echo "Coverage metrics updated in Codecov" diff --git a/pytest.ini b/pytest.ini index 628d695..4cb86a4 100644 --- a/pytest.ini +++ b/pytest.ini @@ -17,6 +17,7 @@ addopts = --disable-warnings -n auto --maxfail=5 + --dist loadgroup # Markers for organizing tests markers = @@ -27,6 +28,9 @@ markers = parametric: Parametric tests across multiple scenarios fast: Quick tests that run in under 1 second main: Tests that cover functionality previously included in main.py + serial: Tests intended to be run serially; enforce with 'pytest -m serial -n 0' + pyomo: Tests requiring Pyomo and IPOPT solver (deselect with '-m not pyomo') + notebook: Tests that execute Jupyter notebooks for documentation # Minimum Python version minversion = 3.8