--- a +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,225 @@ +name: "BioNemo Image Build and Unit Tests" + +on: + push: + branches: + - main + - "pull-request/[0-9]+" + - "dependabot/**" + merge_group: + types: [checks_requested] + schedule: + - cron: "0 7 * * *" # Runs at 7 AM UTC daily (12 AM MST) + +defaults: + run: + shell: bash -x -e -u -o pipefail {0} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: "recursive" + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + - run: pip install -r requirements-dev.txt + - run: ./ci/scripts/static_checks.sh + + # With copy-pr-bot, we need to get the PR labels from the PR API rather than from the event metadata. + get-pr-labels: + runs-on: ubuntu-latest + outputs: + labels: ${{ steps.get-labels.outputs.labels }} + steps: + - name: Get PR number from branch + if: startsWith(github.ref, 'refs/heads/pull-request/') + id: get-pr-num + run: | + PR_NUM=$(echo ${{ github.ref_name }} | grep -oE '[0-9]+$') + echo "pr_num=$PR_NUM" >> $GITHUB_OUTPUT + + - name: Get PR labels + id: get-labels + if: startsWith(github.ref, 'refs/heads/pull-request/') + env: + GH_TOKEN: ${{ github.token }} + run: | + LABELS=$(gh api repos/${{ github.repository }}/pulls/${{ steps.get-pr-num.outputs.pr_num }} --jq '[.labels[].name]' || echo "[]") + echo "labels=$LABELS" >> $GITHUB_OUTPUT + + - name: Set empty labels for non-PR branches + if: ${{ !startsWith(github.ref, 'refs/heads/pull-request/') }} + id: get-labels-empty + run: echo "labels=[]" >> $GITHUB_OUTPUT + + build-bionemo-image: + needs: + - pre-commit + - get-pr-labels + runs-on: linux-amd64-cpu16 + if: ${{ !contains(fromJSON(needs.get-pr-labels.outputs.labels || '[]'), 'SKIP_CI') }} + steps: + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: "recursive" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker Metadata + id: metadata + uses: docker/metadata-action@v5 + with: + images: svcbionemo023/bionemo-framework + tags: | + type=schedule + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=raw,value=${{ github.run_id }} + + # This action sets up our cache-from and cache-to flags appropriately; see the README of this action for more + # info. It doesn't seem to cache correctly for merge_group events, so we need to add that as an extra argument in + # the step below. There's probably a slight optimization to be had here by caching from the pr- caches for + # merge_group events. See https://github.com/int128/docker-build-cache-config-action/issues/1005 for more info. + - uses: int128/docker-build-cache-config-action@v1 + id: cache + with: + image: svcbionemo023/bionemo-build-cache + + - name: Build and push + uses: docker/build-push-action@v5 + with: + push: true + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: | + ${{ steps.cache.outputs.cache-from }} + type=registry,ref=svcbionemo023/bionemo-build-cache:main + cache-to: ${{ steps.cache.outputs.cache-to }} + + + run-tests: + needs: + - build-bionemo-image + - get-pr-labels + runs-on: linux-amd64-gpu-l4-latest-1 + container: + image: svcbionemo023/bionemo-framework:${{ github.run_id }} + credentials: + username: ${{ vars.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run tests + # Tests in this stage generate code coverage metrics for the repository + # Coverage data is uploaded to Codecov in subsequent stages + env: + BIONEMO_DATA_SOURCE: ngc + run: ./ci/scripts/run_pytest.sh --no-nbval --skip-slow + + - name: Upload coverage to Codecov + # Don't run coverage on merge queue or nightly CI to avoid duplicating reports + # to codecov. See https://github.com/matplotlib/napari-matplotlib/issues/155 + if: github.event_name != 'merge_group' && github.event_name != 'schedule' + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload test results to Codecov + # Don't run coverage on merge queue or nightly CI to avoid duplicating reports + # to codecov. See https://github.com/matplotlib/napari-matplotlib/issues/155 + if: ${{ !cancelled() && github.event_name != 'merge_group' && github.event_name != 'schedule' }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + run-slow-tests: + needs: + - build-bionemo-image + - get-pr-labels + runs-on: linux-amd64-gpu-l4-latest-1 + container: + image: svcbionemo023/bionemo-framework:${{ github.run_id }} + credentials: + username: ${{ vars.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + if: | + github.event_name == 'schedule' || github.event_name == 'merge_group' || + contains(fromJSON(needs.get-pr-labels.outputs.labels || '[]'), 'INCLUDE_SLOW_TESTS') + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run slow tests + env: + BIONEMO_DATA_SOURCE: ngc + # Not every sub-package has slow tests, and since some sub-packages have tests under the same name we need + # to run package by package like we do with the fast tests. + run: ./ci/scripts/run_pytest.sh --no-nbval --only-slow --allow-no-tests + + + run-notebooks-docs: + needs: + - build-bionemo-image + - get-pr-labels + runs-on: linux-amd64-gpu-l4-latest-1 + if: | + github.event_name == 'schedule' || github.event_name == 'merge_group' || + contains(fromJSON(needs.get-pr-labels.outputs.labels || '[]'), 'INCLUDE_NOTEBOOKS_TESTS') + container: + image: svcbionemo023/bionemo-framework:${{ github.run_id }} + credentials: + username: ${{ vars.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run notebook tests + env: + BIONEMO_DATA_SOURCE: ngc + # this variable should be used in the notebooks to run a subset of the model layers or a smaller model/dataset + FAST_CI_MODE: true + run: | + pytest -v --nbval-lax -p no:python docs/ sub-packages/ + + verify-tests-status: + # Base on the status of this job, the unit-tests workflow succeeds or fails + # This steps checks the status of all test jobs and fails if any of them failed or were cancelled. + # It is a workaround for the lack of a built-in feature to finalize a pipeline by checking the status of multiple jobs + needs: # List all your run-*-test jobs + - run-tests + - run-slow-tests + - run-notebooks-docs + # Add all other run-*-test jobs + runs-on: ubuntu-latest + if: always() # This ensures the job runs even if previous jobs fail + steps: + - name: Check test job statuses + run: | + if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then + echo "Some test jobs have failed or been cancelled!" + exit 1 + else + echo "All test jobs have completed successfully or been skipped!" + exit 0 + fi