diff --git a/.github/workflows/extra.yml b/.github/workflows/extra.yml index 2168687f2..5e3acbdd1 100644 --- a/.github/workflows/extra.yml +++ b/.github/workflows/extra.yml @@ -54,6 +54,10 @@ jobs: coverage: if: "! github.event.pull_request.draft" runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shard: [0, 1, 2, 3] services: redis: image: redis:7 @@ -78,12 +82,99 @@ jobs: run: pixi run -e py312e ./install/install_minq.sh - name: Install gpCAM run: pixi run -e py312e pip install gpcam==8.1.13 - - name: Run canonical coverage suite - run: pixi run -e py312e ./libensemble/tests/run_tests.py -l -m -e --coverage --fail-fast --durations 25 - - name: Upload coverage reports to Codecov + - name: Run coverage suite (shard 0 - unit + regression slice) + if: matrix.shard == 0 + run: >- + pixi run -e py312e ./libensemble/tests/run_tests.py -l -m -e + --coverage --fail-fast --durations 25 + --shard 0/4 --timings-json timings-coverage-0.json + - name: Run coverage suite (shards 1-3 - regression slice only) + if: matrix.shard != 0 + run: >- + pixi run -e py312e ./libensemble/tests/run_tests.py -r -l -m -e + --coverage --fail-fast --durations 25 + --shard ${{ matrix.shard }}/4 --timings-json timings-coverage-${{ matrix.shard }}.json + - name: Stage coverage files + if: always() + run: | + mkdir -p cov_staging + find libensemble/tests -type f \( -name ".cov_*" -o -name ".coverage*" \) ! -name ".cov_merge_out*" -exec cp --parents {} cov_staging/ \; 2>/dev/null || echo "no .cov files in libensemble/tests" + echo "Staged files:"; find cov_staging -type f | head -n 50; echo "---"; du -sh cov_staging 2>/dev/null || true + - name: Upload coverage data + if: always() + uses: actions/upload-artifact@v7 + with: + name: coverage-data-${{ matrix.shard }} + path: cov_staging/ + include-hidden-files: true + if-no-files-found: warn + - name: Upload timings + if: always() + uses: actions/upload-artifact@v7 + with: + name: coverage-timings-${{ matrix.shard }} + path: timings-coverage-${{ matrix.shard }}.json + if-no-files-found: ignore + + coverage-combine: + if: always() && !cancelled() && needs.coverage.result != 'skipped' + needs: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + lfs: true + - uses: prefix-dev/setup-pixi@v0.11.0 + with: + pixi-version: v0.69.0 + cache: true + frozen: true + environments: py312e + - uses: actions/download-artifact@v7 + with: + pattern: coverage-data-* + path: _coverage_data + merge-multiple: true + - name: Combine coverage reports + run: | + echo "Downloaded artifacts:"; find _coverage_data -type f | head -n 80 + echo "--- restoring to libensemble/tests ---" + mkdir -p libensemble/tests + if [ -d _coverage_data/libensemble ]; then + if command -v rsync >/dev/null 2>&1; then + rsync -a _coverage_data/libensemble/ libensemble/ + else + cp -a _coverage_data/libensemble/tests/. libensemble/tests/ 2>/dev/null || true + cp -rn _coverage_data/libensemble/* libensemble/ 2>/dev/null || true + fi + else + find _coverage_data -type f \( -name ".cov_*" -o -name ".coverage*" \) ! -name ".cov_merge_out*" -exec cp --parents {} . \; 2>/dev/null || true + fi + echo "Files in libensemble/tests:"; ls -lh libensemble/tests/.cov* 2>&1 | head -n 80; find libensemble/tests -name ".cov_*" | head -n 80 + echo "Top-level .coveragerc:"; cat libensemble/tests/.coveragerc | head -n 10; ls -lh libensemble/tests/.coveragerc + (cd libensemble/tests && COV_FILES=$(find . -type f \( -name ".cov_*" -o -name ".coverage*" \) ! -name ".cov_merge_out*" | tr '\n' ' ') && if [ -z "$COV_FILES" ]; then echo "No coverage files found to combine"; find . -type f | head -n 50; exit 1; fi && echo "Combining coverage files:" && echo "$COV_FILES" | tr ' ' '\n' | head -n 50 && pixi run -e py312e coverage combine $COV_FILES) + ls -lh libensemble/tests/.cov_merge_out* libensemble/tests/.coverage 2>&1 | head -n 20 + (cd libensemble/tests && pixi run -e py312e coverage xml -o coverage.xml) + (cd libensemble/tests && pixi run -e py312e coverage report --skip-covered | tail -n 80) + echo "coverage.xml size:"; ls -lh libensemble/tests/coverage.xml + - name: Upload coverage to Codecov uses: codecov/codecov-action@v7 + with: + files: libensemble/tests/coverage.xml + fail_ci_if_error: true env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - name: Upload final timing artifacts + if: always() + uses: actions/download-artifact@v7 + with: + pattern: coverage-timings-* + path: _timings + merge-multiple: true + - name: Merge and display timings + if: always() + run: | + find _timings -name "*.json" -exec cat {} \; 2>/dev/null | head -c 2000 || echo "no timings" external: runs-on: ubuntu-latest diff --git a/libensemble/tests/unit_tests/test_manager_main.py b/libensemble/tests/unit_tests/test_manager_main.py index 45b871938..f2425e672 100644 --- a/libensemble/tests/unit_tests/test_manager_main.py +++ b/libensemble/tests/unit_tests/test_manager_main.py @@ -67,6 +67,10 @@ def test_term_test_3(): def test_send_work_order_persis_stop_empty_rows(): + # Regression test: a PERSIS_STOP work order with zero H_rows (e.g. final_gen_send + # with nothing left to forward to a persistent gen) must still send the meta-message. + # Otherwise, a persistent worker blocked in PersistentSupport.recv() waiting on this + # message hangs forever, since nothing would ever be sent to it. hist, sim_specs, gen_specs, exit_criteria, al = setup.hist_setup2() final_libE_specs = dict(libE_specs, final_gen_send=True) fake_wcomm = FakeComm()