diff --git a/.github/workflows/extra.yml b/.github/workflows/extra.yml index 2168687f2..156514b50 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,93 @@ 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 + if [ -f libensemble/tests/.cov_merge_out ]; then + cp libensemble/tests/.cov_merge_out cov_staging/cov_merge_out_shard${{ matrix.shard }}.db + else + echo "::warning::no merged coverage db (.cov_merge_out) produced by shard ${{ matrix.shard }}" + fi + echo "Staged files:"; ls -lah cov_staging; du -sh cov_staging + - 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 | sort + DBS=$(find _coverage_data -type f -name "cov_merge_out_shard*.db" | sort) + if [ -z "$DBS" ]; then echo "::error::no per-shard coverage databases found"; exit 1; fi + DBS_ABS=$(for f in $DBS; do echo "$PWD/$f"; done) + cd libensemble/tests + pixi run -e py312e coverage combine $DBS_ABS + ls -lh .cov_merge_out + pixi run -e py312e coverage xml -o coverage.xml + pixi run -e py312e coverage report --skip-covered | tail -n 80 + ls -lh 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()